Skip to content

Commit

Permalink
feat(foxy-cart-form): add View and Copy ID buttons to Customer section
Browse files Browse the repository at this point in the history
  • Loading branch information
pheekus committed Sep 21, 2024
1 parent 43ce7ae commit 4d2e9d2
Show file tree
Hide file tree
Showing 6 changed files with 269 additions and 97 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ async function waitForIdle(element: Control) {
}

describe('InternalResourcePickerControl', () => {
it('imports and defines vaadin-button element', () => {
expect(customElements.get('vaadin-button')).to.exist;
});

it('imports and defines foxy-internal-editable-control element', () => {
expect(customElements.get('foxy-internal-resource-picker-control')).to.exist;
});
Expand All @@ -34,6 +38,10 @@ describe('InternalResourcePickerControl', () => {
expect(customElements.get('foxy-internal-form')).to.exist;
});

it('imports and defines foxy-copy-to-clipboard element', () => {
expect(customElements.get('foxy-copy-to-clipboard')).to.exist;
});

it('imports and defines foxy-form-dialog element', () => {
expect(customElements.get('foxy-form-dialog')).to.exist;
});
Expand Down Expand Up @@ -63,11 +71,24 @@ describe('InternalResourcePickerControl', () => {
expect(new Control().getDisplayValueOptions(resource)).to.deep.equal({ resource });
});

it('has a reactive property "showCopyIdButton"', () => {
expect(new Control()).to.have.property('showCopyIdButton', false);
expect(Control).to.have.deep.nested.property('properties.showCopyIdButton', {
attribute: 'show-copy-id-button',
type: Boolean,
});
});

it('has a reactive property "virtualHost"', () => {
expect(Control).to.have.deep.nested.property('properties.virtualHost', {});
expect(new Control()).to.have.property('virtualHost').that.is.a('string');
});

it('has a reactive property "getItemUrl"', () => {
expect(Control).to.have.deep.nested.property('properties.getItemUrl', { attribute: false });
expect(new Control()).to.have.property('getItemUrl', null);
});

it('has a reactive property "formProps"', () => {
expect(Control).to.have.deep.nested.property('properties.formProps', { type: Object });
expect(new Control()).to.have.deep.property('formProps', {});
Expand Down Expand Up @@ -421,6 +442,91 @@ describe('InternalResourcePickerControl', () => {
expect(clearBtn).to.have.attribute('hidden');
});

it('renders View link in standalone layout when value is set and getItemUrl is defined', async () => {
const control = await fixture<Control>(html`
<foxy-internal-resource-picker-control
infer=""
first="https://demo.api/hapi/customers"
item="foxy-customer-card"
.getItemUrl=${(value: string) => value.replace('demo.api', 'example.com')}
>
</foxy-internal-resource-picker-control>
`);

let linkText = control.renderRoot.querySelector('[key="view"]');
expect(linkText).to.not.exist;

control.getValue = () => 'https://demo.api/hapi/customers/0';
await control.requestUpdate();
linkText = control.renderRoot.querySelector('[key="view"]');
expect(linkText).to.exist;
expect(linkText).to.have.attribute('infer', '');

const viewLink = linkText?.closest('a');
expect(viewLink).to.exist;
expect(viewLink).to.have.attribute('href', 'https://example.com/hapi/customers/0');
});

it('renders Copy ID button in standalone layout when value is set and showCopyIdButton is true', async () => {
const control = await fixture<Control>(html`
<foxy-internal-resource-picker-control
infer=""
first="https://demo.api/hapi/customers"
item="foxy-customer-card"
>
</foxy-internal-resource-picker-control>
`);

const selector = 'foxy-copy-to-clipboard[infer="copy-id"]';
let copyBtn = control.renderRoot.querySelector(selector);
expect(copyBtn).to.not.exist;

control.getValue = () => 'https://demo.api/hapi/customers/0';
await control.requestUpdate();
copyBtn = control.renderRoot.querySelector(selector);
expect(copyBtn).to.not.exist;

control.showCopyIdButton = true;
await control.requestUpdate();
copyBtn = control.renderRoot.querySelector(selector);
expect(copyBtn).to.exist;
expect(copyBtn).to.have.attribute('layout', 'text');
expect(copyBtn).to.have.attribute('theme', 'contrast tertiary-inline');
});

it('renders Clear button in standalone layout when value is set', async () => {
const control = await fixture<Control>(html`
<foxy-internal-resource-picker-control
infer=""
first="https://demo.api/hapi/customers"
item="foxy-customer-card"
>
</foxy-internal-resource-picker-control>
`);

let btnText = control.renderRoot.querySelector('foxy-i18n[key="clear"]');
expect(btnText).to.not.exist;

control.getValue = () => 'https://demo.api/hapi/customers/0';
await control.requestUpdate();
btnText = control.renderRoot.querySelector('foxy-i18n[key="clear"]');
expect(btnText).to.exist;
expect(btnText).to.have.attribute('infer', '');

const clearBtn = btnText?.closest('vaadin-button');
expect(clearBtn).to.exist;
expect(clearBtn).to.not.have.attribute('disabled');

const setValueStub = stub();
control.setValue = setValueStub;
clearBtn?.click();
expect(setValueStub).to.have.been.calledOnceWith('');

control.disabled = true;
await control.requestUpdate();
expect(clearBtn).to.have.attribute('disabled');
});

describe('InternalResourcePickerControlForm', () => {
it('defines itself as foxy-internal-resource-picker-control-form', () => {
expect(customElements.get('foxy-internal-resource-picker-control-form')).to.equal(Form);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { FormDialog } from '../../public/FormDialog/FormDialog';
import type { Option } from '../../public/QueryBuilder/types';

import { InternalEditableControl } from '../InternalEditableControl/InternalEditableControl';
import { getResourceId } from '@foxy.io/sdk/core';
import { FetchEvent } from '../../public/NucleonElement/FetchEvent';
import { ifDefined } from 'lit-html/directives/if-defined';
import { html, svg } from 'lit-html';
Expand All @@ -23,7 +24,9 @@ export class InternalResourcePickerControl extends InternalEditableControl {
return {
...super.properties,
getDisplayValueOptions: { attribute: false },
showCopyIdButton: { type: Boolean, attribute: 'show-copy-id-button' },
virtualHost: {},
getItemUrl: { attribute: false },
formProps: { type: Object },
filters: { type: Array },
layout: {},
Expand All @@ -35,8 +38,12 @@ export class InternalResourcePickerControl extends InternalEditableControl {

getDisplayValueOptions: DisplayValueOptionsCb = resource => ({ resource });

showCopyIdButton = false;

virtualHost = uniqueId('internal-resource-picker-control-');

getItemUrl: ((href: string) => string) | null = null;

formProps: Record<string, unknown> = {};

filters: Option[] = [];
Expand Down Expand Up @@ -93,6 +100,11 @@ export class InternalResourcePickerControl extends InternalEditableControl {
if (changes.has('item')) this.__getItemRenderer.cache.clear?.();
}

private __clear(): void {
this._value = '';
this.dispatchEvent(new CustomEvent('clear'));
}

private __renderSummaryItemLayout() {
const resource = this.renderRoot.querySelector<NucleonElement<any>>('#value');
const onClick = (evt: Event) => {
Expand Down Expand Up @@ -155,10 +167,7 @@ export class InternalResourcePickerControl extends InternalEditableControl {
style="width: 1em; height: 1em;"
?disabled=${this.disabled}
?hidden=${this.readonly || !this._value}
@click=${() => {
this._value = '';
this.dispatchEvent(new CustomEvent('clear'));
}}
@click=${this.__clear}
>
${svg`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" style="width: 1em; height: 1em; transform: scale(1.25); margin-right: -0.16em"><path d="M6.28 5.22a.75.75 0 0 0-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 1 0 1.06 1.06L10 11.06l3.72 3.72a.75.75 0 1 0 1.06-1.06L11.06 10l3.72-3.72a.75.75 0 0 0-1.06-1.06L10 8.94 6.28 5.22Z" /></svg>`}
</button>
Expand All @@ -176,28 +185,58 @@ export class InternalResourcePickerControl extends InternalEditableControl {
}

private __renderStandaloneLayout() {
const selectionUrl = typeof this._value === 'string' ? this.getItemUrl?.(this._value) : void 0;
const selectionId = typeof this._value === 'string' ? getResourceId(this._value) : void 0;

return html`
<div class="block group">
<div
class=${classMap({
'transition-colors mb-xs font-medium text-s': true,
'text-secondary group-hover-text-body': !this.disabled && !this.readonly,
'text-secondary': this.readonly,
'flex items-center gap-m transition-colors mb-s font-medium text-l': true,
'text-disabled': this.disabled,
})}
>
${this.label}
<span class="mr-auto">${this.label}</span>
${selectionUrl
? html`
<a
class="text-body rounded transition-opacity hover-opacity-90 focus-outline-none focus-ring-2 focus-ring-primary-50"
href=${selectionUrl}
>
<foxy-i18n infer="" key="view"></foxy-i18n>
</a>
`
: ''}
${this.showCopyIdButton && selectionId !== null
? html`
<foxy-copy-to-clipboard
layout="text"
theme="contrast tertiary-inline"
infer="copy-id"
text=${selectionId}
>
</foxy-copy-to-clipboard>
`
: ''}
${this.readonly || !this._value
? ''
: html`
<vaadin-button
theme="error tertiary-inline"
?disabled=${this.disabled}
@click=${this.__clear}
>
<foxy-i18n infer="" key="clear"></foxy-i18n>
</vaadin-button>
`}
</div>
<button
class=${classMap({
'block w-full rounded text-left transition-colors': true,
'border border-dashed': true,
'border-transparent': !this.readonly,
'cursor-pointer bg-contrast-5 hover-bg-contrast-10': !this.disabled && !this.readonly,
'cursor-default bg-contrast-5': this.disabled,
'cursor-default border-contrast-30': this.readonly,
'block w-full bg-contrast-5 rounded text-left transition-colors': true,
'focus-outline-none focus-ring-2 focus-ring-primary-50': true,
'cursor-pointer hover-bg-contrast-10': !this.disabled && !this.readonly,
'cursor-default': this.disabled || this.readonly,
})}
style="padding: calc(0.625em + (var(--lumo-border-radius) / 4) - 1px)"
?disabled=${this.disabled || this.readonly}
Expand Down
3 changes: 3 additions & 0 deletions src/elements/internal/InternalResourcePickerControl/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import '@vaadin/vaadin-button';

import '../InternalAsyncListControl/index';
import '../InternalEditableControl/index';
import '../InternalForm/index';

import '../../public/CopyToClipboard/index';
import '../../public/NucleonElement/index';
import '../../public/FormDialog/index';
import '../../public/I18n/index';
Expand Down
18 changes: 13 additions & 5 deletions src/elements/public/CartForm/CartForm.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ async function createElement(router = createRouter()) {
regions="https://demo.api/hapi/property_helpers/4"
coupons="https://demo.api/hapi/coupons?store_id=0"
href="https://demo.api/hapi/carts/0?zoom=discounts"
.getCustomerPageUrl=${(id: string) => `/customer/${id}`}
@fetch=${(evt: FetchEvent) => router.handleEvent(evt)}
>
</foxy-cart-form>
Expand Down Expand Up @@ -197,6 +198,13 @@ describe('CartForm', () => {
);
});

it('has a reactive property "getCustomerPageUrl"', () => {
expect(new Form()).to.have.property('getCustomerPageUrl', null);
expect(Form).to.have.deep.nested.property('properties.getCustomerPageUrl', {
attribute: false,
});
});

it('has a reactive property "itemCategories"', () => {
expect(new Form()).to.have.property('itemCategories', null);
expect(Form).to.have.nested.property('properties.itemCategories');
Expand Down Expand Up @@ -534,20 +542,20 @@ describe('CartForm', () => {
);
});

it('renders resource picker control for customer uri inside General summary', async () => {
it('renders resource picker control for customer uri', async () => {
const router = createRouter();
const element = await createElement(router);
await waitForIdle(element);

const control = element.renderRoot.querySelector<InternalResourcePickerControl>(
`[infer="general"] [infer="customer-uri"]`
);
const control =
element.renderRoot.querySelector<InternalResourcePickerControl>('[infer="customer-uri"]');

expect(control).to.exist;
expect(control?.localName).to.equal('foxy-internal-resource-picker-control');
expect(control?.getAttribute('layout')).to.equal('summary-item');
expect(control?.getAttribute('item')).to.equal('foxy-customer-card');
expect(control?.getAttribute('first')).to.equal('https://demo.api/hapi/customers?store_id=0');
expect(control).to.have.attribute('show-copy-id-button');
expect(control).to.have.property('getItemUrl', element.getCustomerPageUrl);

expect(control).to.have.deep.property('filters', [
{ label: 'filter_email', path: 'email', type: Type.String },
Expand Down
25 changes: 15 additions & 10 deletions src/elements/public/CartForm/CartForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export class CartForm extends Base<Data> {
return {
...super.properties,
paymentCardEmbedUrl: { attribute: 'payment-card-embed-url' },
getCustomerPageUrl: { attribute: false },
itemCategories: { attribute: 'item-categories' },
templateSets: { attribute: 'template-sets' },
localeCodes: { attribute: 'locale-codes' },
Expand Down Expand Up @@ -66,6 +67,9 @@ export class CartForm extends Base<Data> {
/** Payment Card Embed configuration URL. The form will append template set parameter on its own. */
paymentCardEmbedUrl: string | null = null;

/** When configured, Customer section will include a link generated by this function. */
getCustomerPageUrl: ((id: string) => string) | null = null;

/** URL of the `fx:item_categories` collection for the store. */
itemCategories: string | null = null;

Expand Down Expand Up @@ -235,16 +239,6 @@ export class CartForm extends Base<Data> {
>
</foxy-internal-select-control>
<foxy-internal-resource-picker-control
layout="summary-item"
first=${ifDefined(this.customers ?? void 0)}
infer="customer-uri"
item="foxy-customer-card"
.setValue=${this.__customerUriSetValue}
.filters=${this.__customerUriOptions}
>
</foxy-internal-resource-picker-control>
<foxy-internal-text-control layout="summary-item" infer="customer-email">
</foxy-internal-text-control>
</foxy-internal-summary-control>
Expand Down Expand Up @@ -281,6 +275,17 @@ export class CartForm extends Base<Data> {
>
</foxy-internal-async-list-control>
<foxy-internal-resource-picker-control
first=${ifDefined(this.customers ?? void 0)}
infer="customer-uri"
item="foxy-customer-card"
show-copy-id-button
.getItemUrl=${this.getCustomerPageUrl}
.setValue=${this.__customerUriSetValue}
.filters=${this.__customerUriOptions}
>
</foxy-internal-resource-picker-control>
<foxy-internal-summary-control infer="billing">
<foxy-internal-resource-picker-control
placeholder=${this.__paymentMethodUriPlaceholder}
Expand Down
Loading

0 comments on commit 4d2e9d2

Please sign in to comment.