Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Migrate result variant provider #1440

Merged
merged 18 commits into from
Apr 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { createResultStub } from '../../../__stubs__/index';
import { findTestDataById, getDataTestSelector, installNewXPlugin } from '../../../__tests__/utils';
import ResultVariantsProvider from '../result-variants-provider.vue';
import ResultVariantSelector from '../result-variant-selector.vue';
import { XPlugin } from '../../../plugins/index';
import { bus } from '../../../plugins/index';

const variants = [
{
Expand Down Expand Up @@ -51,8 +51,7 @@ const renderResultVariantsProvider = ({
autoSelectDepth
}: ResultVariantsProviderOptions): ResultVariantsProviderApi => {
const [, localVue] = installNewXPlugin();

const eventsBusSpy = jest.spyOn(XPlugin.bus, 'emit');
const eventsBusSpy = jest.spyOn(bus, 'emit');

const wrapper = mount(
{
Expand Down Expand Up @@ -105,6 +104,10 @@ const renderResultVariantsProvider = ({
};

describe('results with variants', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('provider exposes the result in the default slot', () => {
const template = `
<span data-test="result-name">{{newResult.name}}</span>
Expand Down Expand Up @@ -349,7 +352,7 @@ describe('results with variants', () => {

it('wont render if no result is injected', () => {
const { wrapper } = renderResultVariantsProvider({
result: null
result: {}
});

expect(wrapper.find(getDataTestSelector('variants-list')).exists()).toBe(false);
Expand Down Expand Up @@ -450,7 +453,7 @@ describe('results with variants', () => {
*/
interface ResultVariantsProviderOptions {
/** The result containing the variants. */
result: Result | null;
result: Result | Record<string, never>;
/** The template to render inside the provider's default slot. */
template?: string;
/** Indicates the number of levels to auto select the first variants. */
Expand Down Expand Up @@ -485,7 +488,7 @@ interface ResultVariantsProviderApi {
*/
setResult: (result: Result) => Promise<void>;
/**
* A Jest spy set in the {@link XPlugin} bus `emit` function,
* A Jest spy set in the {@link bus} bus `emit` function,
* useful to test events emitted in the first lifecycle hooks of the component.
*/
eventsBusSpy: jest.SpyInstance;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
* @public
* @returns The 'selectResultVariant' injection key.
*/
const selectResultVariant = inject<Ref<(variant: ResultVariant, level?: number) => void>>(
const selectResultVariant = inject<(variant: ResultVariant, level?: number) => void>(
SELECT_RESULT_VARIANT_KEY as string
);

Expand All @@ -111,9 +111,9 @@
*/
const variants = computed<ResultVariant[] | undefined>(() => {
if (props.level === 0) {
return result?.value?.variants;
return result!.value?.variants;
}
return selectedVariants?.value[props.level - 1]?.variants;
return selectedVariants!.value[props.level - 1]?.variants;
});

/**
Expand All @@ -133,7 +133,7 @@
* @internal
*/
const selectVariant = (variant: ResultVariant): void => {
selectResultVariant!.value(variant, props.level);
selectResultVariant!(variant, props.level);
};

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
<script lang="ts">
import Vue, { VNode, CreateElement } from 'vue';
import { Component, Prop, Watch } from 'vue-property-decorator';
import {
defineComponent,
ref,
computed,
watch,
provide,
Ref,
CreateElement,
VNode,
PropType
} from 'vue';
import { Result, ResultVariant } from '@empathyco/x-types';
import { XProvide } from '../decorators/injection.decorators';
import {
RESULT_WITH_VARIANTS_KEY,
SELECTED_VARIANTS_KEY,
SELECT_RESULT_VARIANT_KEY
} from '../decorators/injection.consts';
import { useXBus } from '../../composables/use-x-bus';

/**
* Component that exposes the result merged with its selected variant in the default slot.
Expand All @@ -19,64 +28,142 @@
*
* @public
*/
@Component
export default class ResultVariantsProvider extends Vue {
/**
* The original result containing the variants.
*
* @public
*/
@Prop({
required: true
})
@XProvide(RESULT_WITH_VARIANTS_KEY)
public result!: Result;
export default defineComponent({
name: 'ResultVariantsProvider',
props: {
/**
* The original result containing the variants.
*
* @public
*/
result: {
type: Object as PropType<Result>,
required: true
},
/**
* The provider by default will auto select the first variants of all levels.
* This prop allows to limit the number of variants auto selected when the provider is created.
* Take into account that the depth will be the variants level + 1, so, setting autoSelectDepth
* to 0 will not select any variant, setting it to 1 will select only the first variant of the
* first level, and so on.
*/
autoSelectDepth: {
type: Number,
default: Number.POSITIVE_INFINITY
}
},
setup(props) {
const xBus = useXBus();

/**
* The provider by default will auto select the first variants of all levels.
* This prop allows to limit the number of variants auto selected when the provider is created.
* Take into account that the depth will be the variants level + 1, so, setting autoSelectDepth
* to 0 will not select any variant, setting it to 1 will select only the first variant of the
* first level, and so on.
*/
@Prop({
default: Number.POSITIVE_INFINITY
})
public autoSelectDepth!: number;
/**
* The original result containing the variants as a computed ref object to enable watching prop changes.
*
* @public
*/
const result = computed(() => props.result);

/**
* Array to keep track of the selected variants of the result.
* Each position of the array is a nest level in the variants' hierarchy, so,
* the second position will contain a variant that is present inside the variant of the first
* position, and so on.
*
* @public
*/
@XProvide(SELECTED_VARIANTS_KEY)
public selectedVariants: ResultVariant[] = [];
/**
* Array to keep track of the selected variants of the result.
* Each position of the array is a nest level in the variants' hierarchy, so,
* the second position will contain a variant that is present inside the variant of the first
* position, and so on.
*
* @public
*/
const selectedVariants = ref<ResultVariant[]>([]);

/**
* Selects a variant of the result.
* When called, it slices the array of selected variants to remove the selected child variants.
* Emits the {@link XEventsTypes.UserSelectedAResultVariant} when called.
*
* @param variant - The variant to set.
* @param level - The nest level where the variant is placed inside the result.
* @public
*/
@XProvide(SELECT_RESULT_VARIANT_KEY)
selectResultVariant(variant: ResultVariant, level = 0): void {
if (this.selectedVariants[level] === variant) {
return;
}
this.selectedVariants.splice(level, Number.POSITIVE_INFINITY, variant);
this.$x.emit('UserSelectedAResultVariant', {
variant,
level,
result: this.result
/**
* Selects a variant of the result.
* When called, it slices the array of selected variants to remove the selected child variants.
* Emits the {@link XEventsTypes.UserSelectedAResultVariant} when called.
*
* @param variant - The variant to set.
* @param level - The nest level where the variant is placed inside the result.
* @public
*/
const selectResultVariant = (variant: ResultVariant, level = 0): void => {
if (selectedVariants.value[level] === variant) {
return;
}
selectedVariants.value.splice(level, Number.POSITIVE_INFINITY, variant);
xBus.emit('UserSelectedAResultVariant', { variant, level, result: result.value });
};

/**
* Merges the original result with the selected variant.
* The merge is done with all the selected variants of the array.
*
* @returns - The result with the selected variant merged.
* @public
*/
const resultToProvide = computed<Result>(() => {
if (!selectedVariants.value.length) {
return result.value;
}
const mergedResult = selectedVariants.value.reduce<Result>((result, variant) => {
return {
...result,
...variant
};
}, result.value);
mergedResult.variants = result.value.variants;
return mergedResult;
});
}

/**
* Adds to the selectedVariants array the variants up to the autoSelectDepth level.
*
* @param variant - Variant to add to the array.
*/
const selectFirstVariants = (variant?: ResultVariant): void => {
if (!!variant && selectedVariants.value.length <= props.autoSelectDepth - 1) {
selectedVariants.value.push(variant);
selectFirstVariants(variant.variants?.[0]);
}
};

/**
* Provides the original result passed as a prop.
*
* @public
*/
provide<Ref<Result>>(RESULT_WITH_VARIANTS_KEY as string, result);

/**
* The selected variants of the result.
*
* @public
*/
provide<Ref<ResultVariant[]>>(SELECTED_VARIANTS_KEY as string, selectedVariants);

/**
* The result variant key that will be selected.
*
* @public
*/
provide(SELECT_RESULT_VARIANT_KEY as string, selectResultVariant);

/**
* Resets the selected variants when the result changes.
* That includes doing the auto selection of the variants when the component is created
* and when the result is changed.
*/
watch(
result,
() => {
selectedVariants.value = [];
selectFirstVariants(result.value?.variants?.[0]);
},
{ immediate: true }
);

return {
props,
selectedVariants,
selectResultVariant,
resultToProvide
};
},
/**
* Render function of the provider.
* It exposes the result with the selected variant merged.
Expand All @@ -92,51 +179,7 @@
})?.[0] ?? createElement()
);
}

/**
* Resets the selected variants when the result changes.
* That includes doing the auto selection of the variants when the component is created
* and when the result is changed.
*/
@Watch('result', { immediate: true })
resetSelectedVariants(): void {
this.selectedVariants = [];
this.selectFirstVariants(this.result?.variants?.[0]);
}

/**
* Merges the original result with the selected variant.
* The merge is done with all the selected variants of the array.
*
* @returns - The result with the selected variant merged.
* @public
*/
public get resultToProvide(): Result {
if (!this.selectedVariants.length) {
return this.result;
}
const mergedResult = this.selectedVariants.reduce<Result>((result, variant) => {
return {
...result,
...variant
};
}, this.result);
mergedResult.variants = this.result.variants;
return mergedResult;
}

/**
* Adds to the selectedVariants array the variants up to the autoSelectDepth level.
*
* @param variant - Variant to add to the array.
*/
selectFirstVariants(variant?: ResultVariant): void {
if (!!variant && this.selectedVariants.length <= this.autoSelectDepth - 1) {
this.selectedVariants.push(variant);
this.selectFirstVariants(variant.variants?.[0]);
}
}
}
});
</script>

<docs lang="mdx">
Expand Down
Loading