Skip to content

Commit

Permalink
Replace Wikit Lookup component with Codex
Browse files Browse the repository at this point in the history
Replace usages of Wikit's Lookup compontent in `ItemLookup` and
`SpellingVariantInput` with the `CdxLookup` component.

Bug: T370057
Depends-On: Iddbd4a83a444b382d0b9318cd1831b42126464c4
  • Loading branch information
codders committed Oct 2, 2024
1 parent a830f7b commit da188e0
Show file tree
Hide file tree
Showing 8 changed files with 219 additions and 175 deletions.
20 changes: 13 additions & 7 deletions cypress/e2e/NewLexemeForm.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,16 @@ describe( 'NewLexemeForm', () => {
cy.get( '.wbl-snl-language-lookup input' )
.type( '=Q123', { delay: 0 } );
checkA11y( '.wbl-snl-language-lookup' );
cy.get( '.wbl-snl-language-lookup .wikit-OptionsMenu__item' ).click();
cy.get( 'li.cdx-menu-item' ).contains( 'No match was found' ).should( 'not.exist' );
checkA11y( '.wbl-snl-language-lookup' );
cy.get( '.wbl-snl-language-lookup .cdx-menu-item' ).click();

cy.wait( '@LanguageCodeRetrieval' );

cy.get( '.wbl-snl-lexical-category-lookup input' )
.type( '=Q456', { delay: 0 } );
cy.get( '.wbl-snl-lexical-category-lookup .wikit-OptionsMenu__item' ).click();
cy.get( 'li.cdx-menu-item' ).contains( 'No match was found' ).should( 'not.exist' );
cy.get( '.wbl-snl-lexical-category-lookup .cdx-menu-item' ).click();

cy.get( '.wbl-snl-form' )
.submit();
Expand All @@ -99,17 +102,20 @@ describe( 'NewLexemeForm', () => {

cy.get( '.wbl-snl-language-lookup input' )
.type( '=Q123', { delay: 0 } );
cy.get( '.wbl-snl-language-lookup .wikit-OptionsMenu__item' ).click();
cy.get( '.wbl-snl-language-lookup li.cdx-menu-item' ).contains( 'No match was found' ).should( 'not.exist' );
cy.get( '.wbl-snl-language-lookup .cdx-menu-item' ).click();

cy.get( '.wbl-snl-lexical-category-lookup input' )
.type( '=Q456', { delay: 0 } );
cy.get( '.wbl-snl-lexical-category-lookup .wikit-OptionsMenu__item' ).click();
cy.get( '.wbl-snl-lexical-category-lookup li.cdx-menu-item' ).contains( 'No match was found' ).should( 'not.exist' );
cy.get( '.wbl-snl-lexical-category-lookup .cdx-menu-item' ).click();

cy.wait( '@LanguageCodeRetrieval' );

cy.get( '.wbl-snl-spelling-variant-lookup input' )
.type( 'en-ca', { delay: 0 } );
cy.get( '.wbl-snl-spelling-variant-lookup .wikit-OptionsMenu__item' ).click();
cy.get( '.wbl-snl-spelling-variant-lookup li.cdx-menu-item' ).contains( 'No match was found' ).should( 'not.exist' );
cy.get( '.wbl-snl-spelling-variant-lookup .cdx-menu-item' ).click();

cy.get( '.wbl-snl-form' )
.submit();
Expand Down Expand Up @@ -151,12 +157,12 @@ describe( 'NewLexemeForm', () => {

cy.get( '.wbl-snl-language-lookup input' ).click();

cy.get( '.wbl-snl-language-lookup .wikit-OptionsMenu__item__label' )
cy.get( '.wbl-snl-language-lookup .cdx-menu-item__text__label' )
.then( ( $element ) => {
expect( $element ).to.have.text( 'test language' );
} );

cy.get( '.wbl-snl-language-lookup .wikit-OptionsMenu__item__description' )
cy.get( '.wbl-snl-language-lookup .cdx-menu-item__text__description' )
.then( ( $element ) => {
expect( $element ).to.have.text( 'test language description' );
} );
Expand Down
129 changes: 85 additions & 44 deletions src/components/ItemLookup.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
<script setup lang="ts">
import {
computed,
ComputedRef,
ref,
watch,
} from 'vue';
import { SearchedItemOption } from '@/data-access/ItemSearcher';
import WikitLookup from './WikitLookup';
import {
CdxLookup,
CdxField,
MenuItemData,
ValidationStatusType,
ValidationMessages,
} from '@wikimedia/codex';
import RequiredAsterisk from '@/components/RequiredAsterisk.vue';
import debounce from 'lodash/debounce';
import escapeRegExp from 'lodash/escapeRegExp';
import { useMessages } from '@/plugins/MessagesPlugin/Messages';
Expand All @@ -19,6 +28,7 @@ interface Props {
itemSuggestions?: SearchedItemOption[];
ariaRequired?: boolean;
}
const props = withDefaults( defineProps<Props>(), {
error: null,
itemSuggestions: () => [],
Expand All @@ -33,6 +43,8 @@ const emit = defineEmits( {
'update:searchInput': null,
} );
const selection = ref( null );
// itemSuggestions matching the current searchInput
const suggestedOptions = computed( () => {
// eslint-disable-next-line security/detect-non-literal-regexp -- escapeRegExp used
Expand Down Expand Up @@ -72,7 +84,7 @@ const onOptionSelected = ( value: SearchedItemOption | null ) => {
const debouncedSearchForItems = debounce( async ( debouncedInputValue: string ) => {
searchedOptions.value = await props.searchForItems( debouncedInputValue );
}, 150 );
const onSearchInput = ( inputValue: string ) => {
const onInput = ( inputValue: string ) => {
emit( 'update:searchInput', inputValue );
if ( inputValue.trim() === '' ) {
searchedOptions.value = [];
Expand All @@ -88,71 +100,100 @@ const onSearchInput = ( inputValue: string ) => {
debouncedSearchForItems( inputValue );
};
const onScroll = async () => {
const onLoadMore = async () => {
const searchReults = await props.searchForItems(
props.searchInput,
searchedOptions.value.length,
);
searchedOptions.value = [ ...searchedOptions.value, ...searchReults ];
};
// the remaining setup translates multilingual SearchedItemOptions to monolingual WikitItemOptions
interface WikitMenuItem {
label: string;
description: string;
tag?: string;
}
interface WikitItemOption extends WikitMenuItem {
value: string;
}
function searchResultToMonolingualOption( searchResult: SearchedItemOption ): WikitItemOption {
// the remaining setup translates multilingual SearchedItemOptions to monolingual MenuItemData
function searchResultToMonolingualOption( searchResult: SearchedItemOption ): MenuItemData {
return {
label: searchResult.display.label?.value || searchResult.id,
description: searchResult.display.description?.value || '',
value: searchResult.id,
};
}
const wikitMenuItems = computed( () => {
const codexMenuItems: ComputedRef<MenuItemData[]> = computed( () => {
return menuItems.value.map( searchResultToMonolingualOption );
} );
const wikitValue = computed( () => {
if ( props.value === null ) {
return null;
}
return searchResultToMonolingualOption( props.value );
} );
const onWikitOptionSelected = ( value: unknown ) => {
const wikitOption = value as WikitItemOption | null;
const searchOption = menuItems.value.find( ( item ) => item.id === wikitOption?.value );
const onCodexOptionSelected = ( selectedItem: string | null ) => {
const searchOption = menuItems.value.find( ( item ) => item.id === selectedItem );
return onOptionSelected( searchOption ?? null );
};
const messages = useMessages();
const menuConfig = {
visibleItemLimit: 6,
};
const fieldStatus = computed( (): ValidationStatusType => {
if ( !props.error ) {
return 'default';
}
return props.error.type;
} );
const errorMessages = computed( (): ValidationMessages => {
if ( props.error ) {
if ( props.error.type === 'error' ) {
return { error: props.error.message };
}
if ( props.error.type === 'warning' ) {
return { warning: props.error.message };
}
}
return {};
} );
/**
* We want to pass the searchInput property from the parent component
* to the child component. The searchInput property comes in read-only
* and receives updates from the parent (it is a ref / computed value).
*
* In the child, we want to pass the property in as read/write, so that
* it can be updated by in `v-model:input-value`. To this end, we create
* a copy of the property and add a watcher so that updates from the
* parent are propagated, and updates from the child are possible.
*/
const searchInputCopy = ref( props.searchInput );
watch( () => props.searchInput, ( newValue ) => {
searchInputCopy.value = newValue;
}, { immediate: true } );
</script>

<template>
<wikit-lookup
:label="label"
:placeholder="placeholder"
:search-input="props.searchInput"
:menu-items="wikitMenuItems"
:value="wikitValue"
:error="error"
:aria-required="ariaRequired"
@update:search-input="onSearchInput"
@scroll="onScroll"
@input="onWikitOptionSelected"
>
<template #no-results>
{{ messages.getUnescaped( 'wikibase-entityselector-notfound' ) }}
<cdx-field
:status="fieldStatus"
:messages="errorMessages">
<cdx-lookup
v-model:selected="selection"
v-model:input-value="searchInputCopy"
:placeholder="placeholder"
:menu-items="codexMenuItems"
:menu-config="menuConfig"
@load-more="onLoadMore"
@input="onInput"
@update:selected="onCodexOptionSelected"
>
<template #no-results>
{{ messages.getUnescaped( 'wikibase-entityselector-notfound' ) }}
</template>
</cdx-lookup>
<template #label>
{{ label }}<required-asterisk v-if="ariaRequired" />
</template>
<template #suffix>
<slot name="suffix" />
</template>
</wikit-lookup>
</cdx-field>
</template>

<style scoped lang="scss">
.wbl-snl-required-asterisk {
margin-inline-start: var( --dimension-spacing-xsmall );
}
</style>
4 changes: 2 additions & 2 deletions src/components/NewLexemeForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ const spellingVariant = computed( {
get(): string {
return store.state.spellingVariant;
},
set( newSpellingVariant: string | null ): void {
set( newSpellingVariant: string | undefined ): void {
store.commit( SET_SPELLING_VARIANT, newSpellingVariant );
if ( newSpellingVariant ) {
store.commit( CLEAR_PER_FIELD_ERRORS, 'spellingVariantErrors' );
Expand Down Expand Up @@ -158,7 +158,7 @@ export default {
/>
<language-input
v-model="language"
v-model:search-input="languageSearchInput"
:search-input="languageSearchInput"
/>
<spelling-variant-input
v-if="showSpellingVariantInput"
Expand Down
Loading

0 comments on commit da188e0

Please sign in to comment.