Skip to content

Commit

Permalink
feat(search-box): migrate search-box x-module components to Compositi…
Browse files Browse the repository at this point in the history
…on API (#1476)
  • Loading branch information
andreadlgdo authored May 23, 2024
1 parent bfc043f commit 5c2b7bc
Show file tree
Hide file tree
Showing 11 changed files with 602 additions and 538 deletions.
8 changes: 7 additions & 1 deletion packages/_vue3-migration-test/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import {
TestSortDropdown,
TestSortList,
TestSortPickerList,
TestBaseScroll
TestBaseScroll,
TestSearchBox
} from './';

const routes = [
Expand Down Expand Up @@ -94,6 +95,11 @@ const routes = [
name: 'SortPickerList',
component: TestSortPickerList
},
{
path: '/search-box',
name: 'SearchBox',
component: TestSearchBox
},
{
path: '/elements-list',
name: 'ElementsList',
Expand Down
1 change: 1 addition & 0 deletions packages/_vue3-migration-test/src/x-modules/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './facets';
export * from './next-queries';
export * from './search';
export * from './search-box';
export { default as TestElementsList } from './test-elements-list.vue';
export * from './scroll';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as TestSearchBox } from './test-search-box.vue';
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<template>
<div style="display: flex; flex-flow: row nowrap">
<SearchInput
@UserPressedEnterKey="value = 'You have pressed the enter key'"
@UserFocusedSearchBox="hasFocus = true"
@UserBlurredSearchBox="hasFocus = false"
@UserIsTypingAQuery="value = 'Typing...'"
:maxLength="10"
/>
<ClearSearchInput @UserPressedClearSearchBoxButton="value = 'You clear your query'" />
<SearchButton @UserPressedSearchButton="value = 'You search a query'">Search</SearchButton>
<SearchInputPlaceholder
:messages="placeholderMessages"
animation="div"
style="position: absolute; pointer-events: none; top: -41rem; left: 1rem"
/>
</div>
<br />
<span>{{ value }}</span>
<br />
<span>{{ hasFocus ? 'Focused' : 'Unfocused' }}</span>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import SearchInput from '../../../../../x-components/src/x-modules/search-box/components/search-input.vue';
import SearchInputPlaceholder from '../../../../../x-components/src/x-modules/search-box/components/search-input-placeholder.vue';
import SearchButton from '../../../../../x-components/src/x-modules/search-box/components/search-button.vue';
import ClearSearchInput from '../../../../../x-components/src/x-modules/search-box/components/clear-search-input.vue';
const value = ref('There are no value yet');
const hasFocus = ref(false);
const placeholderMessages = [
'Find shirts',
'Find shoes',
'Find watches',
'Find handbags',
'Find sunglasses'
];
</script>

<style scoped></style>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './components';
103 changes: 41 additions & 62 deletions packages/x-components/src/composables/__tests__/use-state.spec.ts
Original file line number Diff line number Diff line change
@@ -1,75 +1,54 @@
import { ComputedRef, defineComponent } from 'vue';
import Vuex, { Store } from 'vuex';
import { createLocalVue, mount } from '@vue/test-utils';
import { Dictionary } from '@empathyco/x-utils';
import { defineComponent } from 'vue';
import { mount } from '@vue/test-utils';
import { installNewXPlugin } from '../../__tests__/utils';
import { useState } from '../use-state';
import { searchBoxXStoreModule } from '../../x-modules/search-box/index';
import { AnyXStoreModule } from '../../store/index';
import { XPlugin } from '../../plugins';
import { ExtractState } from '../../x-modules/x-modules.types';

const renderUseStateTest = (modulePropertyPaths: string[]): renderUseStateTestAPI => {
const testComponent = defineComponent({
setup() {
const searchBoxState = useState(
'searchBox',
modulePropertyPaths as (keyof ExtractState<'searchBox'>)[]
);
return {
searchBoxState
};
}
});

const localVue = createLocalVue();
localVue.use(Vuex);

const store = new Store({
modules: {
x: {
namespaced: true,
modules: {
searchBox: { namespaced: true, ...searchBoxXStoreModule } as AnyXStoreModule
}
}
}
import { useRegisterXModule } from '../use-register-x-module';
import { useState } from '../use-state';
import { searchBoxXModule } from '../../x-modules/search-box/x-module';
import { useStore } from '../use-store';

jest.mock('../use-store');

function render(modulePaths: (keyof ExtractState<'searchBox'> & string)[]) {
installNewXPlugin();
(useStore as jest.Mock).mockReturnValue(XPlugin.store);

const component = defineComponent({
xModule: 'searchBox',
setup: () => {
useRegisterXModule(searchBoxXModule);
const searchBoxUseState = useState('searchBox', modulePaths);
return { searchBoxUseState };
},
template: `<div/>`
});
installNewXPlugin({ store }, localVue);

const wrapper = mount(testComponent, {
localVue,
store
});
const wrapper = mount(component);

return {
searchBoxState: (wrapper.vm as any).searchBoxState,
store
};
};
return (wrapper as any).vm.searchBoxUseState;
}

describe('testing useState composable', () => {
it('maps store state', () => {
const { searchBoxState, store } = renderUseStateTest(['query', 'inputStatus']);
expect(searchBoxState.query.value).toEqual('');
expect(searchBoxState.inputStatus.value).toEqual('initial');
it('should map paths of the store state given', () => {
const { query, inputStatus } = render(['query', 'inputStatus']);

expect(query).toBeDefined();
expect(inputStatus).toBeDefined();
expect(query.value).toEqual('');
expect(inputStatus.value).toEqual('initial');

// eslint-disable-next-line @typescript-eslint/no-unsafe-call
store.commit('x/searchBox/setQuery', 'pork shoulder ');
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
store.commit('x/searchBox/setInputStatus', 'filled');
XPlugin.store.commit('x/searchBox/setQuery', 'pork shoulder');
XPlugin.store.commit('x/searchBox/setInputStatus', 'filled');

expect(searchBoxState.query.value).toEqual('pork shoulder ');
expect(searchBoxState.inputStatus.value).toEqual('filled');
expect(query.value).toEqual('pork shoulder');
expect(inputStatus.value).toEqual('filled');
});

it('does not return not requested state properties', () => {
const { searchBoxState } = renderUseStateTest(['query']);
expect(searchBoxState.query).toBeDefined();
expect(searchBoxState.inputStatus).toBeUndefined();
it('should not map paths which were not requested', () => {
const { query, inputStatus } = render(['query']);

expect(query).toBeDefined();
expect(inputStatus).toBeUndefined();
});
});

type renderUseStateTestAPI = {
searchBoxState: Dictionary<ComputedRef<string[]>>;
store: Store<any>;
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { createLocalVue, mount, Wrapper } from '@vue/test-utils';
import Vuex, { Store } from 'vuex';
import { getXComponentXModuleName, isXComponent } from '../../../../components/x-component.utils';
import { RootXStoreState } from '../../../../store/store.types';
import { installNewXPlugin } from '../../../../__tests__/utils';
import { getDataTestSelector, installNewXPlugin } from '../../../../__tests__/utils';
import { WireMetadata } from '../../../../wiring/wiring.types';
import SearchInput from '../search-input.vue';
import { resetXSearchBoxStateWith } from './utils';
Expand All @@ -28,7 +28,7 @@ function mountNewSearchInput(overrideProps: Partial<SearchInputProps> = {}): Tes
localVue,
propsData: overrideProps
});
const input = wrapper.vm.$refs.input as HTMLInputElement;
const input = wrapper.find(getDataTestSelector('search-input')).element as HTMLInputElement;

return {
wrapper,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,11 @@
</template>

<script lang="ts">
import Vue from 'vue';
import { Component } from 'vue-property-decorator';
import { defineComponent, computed, ref } from 'vue';
import BaseEventButton from '../../../components/base-event-button.vue';
import { State } from '../../../components/decorators/store.decorators';
import { xComponentMixin } from '../../../components/x-component.mixin';
import { VueCSSClasses } from '../../../utils/types';
import { XEventsTypes } from '../../../wiring/events.types';
import { useRegisterXModule } from '../../../composables/use-register-x-module';
import { useState } from '../../../composables/use-state';
import { searchBoxXModule } from '../x-module';
/**
Expand All @@ -31,33 +29,36 @@
*
* @public
*/
@Component({
export default defineComponent({
name: 'ClearSearchInput',
components: { BaseEventButton },
mixins: [xComponentMixin(searchBoxXModule)]
})
export default class ClearSearchInput extends Vue {
@State('searchBox', 'query')
public query!: string;
protected get isQueryEmpty(): boolean {
return this.query.length === 0;
}
xModule: searchBoxXModule.name,
setup: function () {
useRegisterXModule(searchBoxXModule);
const { query } = useState('searchBox', ['query']);
/**
* The events dictionary that are going to be emitted when the button is pressed.
*
* @internal
*/
const clearSearchInputEvents = ref({
UserPressedClearSearchBoxButton: undefined
});
const isQueryEmpty = computed(() => query.value.length === 0);
const dynamicClasses = computed<VueCSSClasses>(() => ({
'x-clear-search-input--has-empty-query': isQueryEmpty.value
}));
protected get dynamicClasses(): VueCSSClasses {
return {
'x-clear-search-input--has-empty-query': this.isQueryEmpty
dynamicClasses,
clearSearchInputEvents
};
}
/**
* The events dictionary that are going to be emitted when the button is pressed.
*
* @internal
*/
protected clearSearchInputEvents: Partial<XEventsTypes> = {
UserPressedClearSearchBoxButton: undefined
};
}
});
</script>

<docs lang="mdx">
Expand Down
Loading

0 comments on commit 5c2b7bc

Please sign in to comment.