@apptus/esales-api
Version:
Library for making requests to Elevate 4 API v3
669 lines (618 loc) • 23.4 kB
text/typescript
// deno-lint-ignore-file
import { describe, it, stub, assertSpyCalls, assertSpyCallArgs } from '#test';
import { Query } from './query.ts';
import type * as m from './models/mod.ts';
import type { AddToCartPopupParams, AddToCartPopupBody } from './models/add-to-cart-popup.ts';
import type { AutocompleteParams, AutocompleteBody } from './models/autocomplete.ts';
import type { NavigationTreeParams } from './models/navigation-tree.ts';
import type { SearchPageParams, SearchPageBody } from './models/search-page.ts';
let fetchSpy: ReturnType<typeof createSpy>;
let response: Promise<Response>;
const createSpy = () => stub(globalThis, 'fetch', () => response);
const suite = describe({
name: 'query',
beforeEach() {
fetchSpy = createSpy();
response = Promise.resolve(new Response('{}', { status: 200 }));
},
afterEach() {
fetchSpy.restore();
}
});
const addToCartPopupSuite = describe(suite, 'addToCartPopup()');
const autocompleteSuite = describe(suite, 'autocomplete()');
const navigationTreeSuite = describe(suite, 'navigationTree()');
const searchPageSuite = describe(suite, 'searchPage()');
const productPageSuite = describe(suite, 'productPage()');
const cartPageSuite = describe(suite, 'cartPage()');
const landingPageSuite = describe(suite, 'landingPage()');
const sponsoredPageSuite = describe(suite, 'sponsoredPage()');
const contentInformationSuite = describe(suite, 'contentInformation()');
const contentSearchPageSuite = describe(suite, 'contentSearchPage()');
// TODO(csv): Use a single method for checking `ProductFilter` type and values?
it.ignore('should allow all valid productFilter values for page bodies', () => {});
it(addToCartPopupSuite, 'should pass on correct endpoint, params and body parameters to Request', async () => {
const query = new Query({
clusterUrl: 'http://localhost:13',
market: 'fr',
locale: 'fr-FR',
touchpoint: 'mobile',
session: () => ({ customerKey: 'c13', sessionKey: 's13' })
});
const params = Object.freeze<AddToCartPopupParams>({ variantKey: 'some_special_key' });
const body = Object.freeze<AddToCartPopupBody>([{
id: '1313',
algorithm: 'ADD_TO_CART_RECS'
}]);
await query.addToCartPopup(params, body as AddToCartPopupBody);
const expectedUrl = new URL('http://localhost:13/api/storefront/v3/queries/add-to-cart-popup?market=fr&customerKey=c13&sessionKey=s13&locale=fr-FR&touchpoint=mobile&variantKey=some_special_key');
const expectedBody = '{"recommendationLists":[{"id":"1313","algorithm":"ADD_TO_CART_RECS"}]}';
assertSpyCalls(fetchSpy, 1);
assertSpyCallArgs(fetchSpy, 0, [
expectedUrl,
{ method: 'POST', body: expectedBody, headers: { 'Content-Type': 'text/plain' } }
]);
});
it(addToCartPopupSuite, 'should allow all API parameters and body options (kitchen sink)', async () => {
const query = new Query({
clusterUrl: 'http://localhost:13',
market: 'fr',
locale: 'fr-FR',
touchpoint: 'mobile',
session: () => ({ customerKey: 'c13', sessionKey: 's13' })
});
const result = await query.addToCartPopup({
variantKey: 'very_special_key',
cart: ['cart-one'],
channels: 'ONLINE|STORE',
notify: true,
presentCustom: ['something', 'anything'],
presentPrices: ['low', 'high'],
priceId: 'highPrice',
stores: 'store-one|store-two',
templateId: 'tpl-2',
viewId: 'preview',
}, [{
id: '1313',
algorithm: 'ADD_TO_CART_RECS',
label: 'Do you have everything you need?',
limit: 9,
params: {
cart: ['cart-one'],
productKey: 'pk1234'
},
productRules: 'rule incl newness 10d ',
showMoreLink: '/add-to-cart-popup',
visualization: 'GRID'
}]
);
result satisfies m.RecommendationListPage;
result.recommendationLists satisfies m.RecommendationList[];
});
it(autocompleteSuite, 'should pass on correct endpoint, params, and body parameters to Request', async() => {
const query = new Query({
clusterUrl: 'http://localhost:10',
market: 'se',
locale: 'sv-SE',
touchpoint: 'desktop',
session: () => ({ customerKey: 'c10', sessionKey: 's10' })
});
const params = Object.freeze<AutocompleteParams>({
q: 'foo',
notify: false,
viewId: 'production',
priceId: 'default',
channels: 'ONLINE',
stores: 'lund',
presentCustom: ['material']
});
const body = Object.freeze<AutocompleteBody>({
contentLists: [{
id: '1',
}],
productFilter: { size: ['XL'] }
});
await query.autocomplete(params, body);
const expectedUrl = new URL('http://localhost:10/api/storefront/v3/queries/autocomplete?market=se&customerKey=c10&sessionKey=s10&locale=sv-SE&touchpoint=desktop&q=foo¬ify=false&viewId=production&priceId=default&channels=ONLINE&stores=lund&presentCustom=material');
const expectedBody = '{"contentLists":[{"id":"1"}],"productFilter":{"size":["XL"]}}';
assertSpyCalls(fetchSpy, 1);
assertSpyCallArgs(fetchSpy, 0, [
expectedUrl,
{ method: 'POST', body: expectedBody, headers: { 'Content-Type': 'text/plain' } }
]);
});
it(autocompleteSuite, 'should allow all API parameters and body options (kitchen sink)', async() => {
const query = new Query({
clusterUrl: 'http://localhost:11',
market: 'se2',
locale: 'sv-SE2',
touchpoint: 'desktop',
session: () => ({ customerKey: 'c11', sessionKey: 's11' })
});
const result = await query.autocomplete({
q: 'robe',
notify: false,
viewId: 'production',
priceId: 'default',
channels: 'ONLINE',
stores: 'storeA|storeB',
templateId: 'my-data-template',
presentCustom: ['foo', 'bar'],
presentPrices: ['discount']
}, {
contentLists: [{ id: 'asd', algorithm: 'NEWEST_CONTENT', limit: 2, contentFilter: { type: 'article' } }],
productFilter: {
brand: 'Nike',
color: ['RED', 'GREEN'],
price: { min: 10, max: 100 }
},
productRules: 'rule incl newness 10d '
});
result satisfies m.Autocomplete;
result.contentSuggestions satisfies m.ContentSuggestion[];
result.phraseSuggestions satisfies m.PhraseSuggestion[];
result.productSuggestions satisfies m.ProductGroup[];
result.contentLists satisfies m.ContentList[];
result.totalHits satisfies number;
result.redirectLink satisfies string | undefined;
});
it(navigationTreeSuite, 'should pass on correct endpoint, and params, parameters to Request', async() => {
const query = new Query({
clusterUrl: 'http://localhost:20',
market: 'en',
locale: 'en-US',
touchpoint: 'desktop',
session: () => ({ customerKey: 'c20', sessionKey: 's20' })
});
const params = Object.freeze<NavigationTreeParams>({
priceId: 'SEK',
notify: true
});
await query.navigationTree(params);
const expectedUrl = new URL('http://localhost:20/api/storefront/v3/queries/navigation-tree?market=en&customerKey=c20&sessionKey=s20&locale=en-US&touchpoint=desktop&priceId=SEK¬ify=true');
assertSpyCalls(fetchSpy, 1);
assertSpyCallArgs(fetchSpy, 0, [expectedUrl, { method: 'GET' }]);
});
it(navigationTreeSuite, 'should allow all API parameters and body options (kitchen sink)', async () => {
const query = new Query({
clusterUrl: 'http://localhost:21',
market: 'en2',
locale: 'en-US2',
touchpoint: 'desktop',
session: () => ({ customerKey: 'c21', sessionKey: 's21' })
});
const result = await query.navigationTree({
notify: false,
viewId: 'production',
priceId: 'default',
channels: 'STORE',
stores: 'storeZ|storeD'
});
result satisfies m.NavigationTree;
result.tree satisfies m.NavigationNode;
});
it(searchPageSuite, 'should pass on correct endpoint, facets, params, and body parameters to Request', async() => {
const query = new Query({
clusterUrl: 'http://localhost:30',
market: 'dk',
locale: 'da-DK',
touchpoint: 'mobile',
session: () => ({ customerKey: 'c30', sessionKey: 's30' })
});
const params = Object.freeze<SearchPageParams>({
q: 'shirt',
limit: 11,
facets: {
color: ['steelblue']
}
});
const body = Object.freeze<SearchPageBody>({ primaryList: { include: true } });
await query.searchPage(params, body);
const expectedUrl = new URL('http://localhost:30/api/storefront/v3/queries/search-page?market=dk&customerKey=c30&sessionKey=s30&locale=da-DK&touchpoint=mobile&q=shirt&limit=11&f.color=steelblue');
const expectedBody = '{"primaryList":{"include":true}}';
assertSpyCalls(fetchSpy, 1);
assertSpyCallArgs(fetchSpy, 0, [
expectedUrl,
{ method: 'POST', body: expectedBody, headers: { 'Content-Type': 'text/plain' } }
]);
});
it(searchPageSuite, 'should allow all API parameters and body options (kitchen sink)', async() => {
const sort: 'RELEVANCE' | 'NEWEST_FIRST' | 'PRICE_ASCENDING' | 'PRICE_DESCENDING' | 'DISCOUNT' | 'RATING' = 'RELEVANCE';
const origin: 'ORGANIC' | 'PHRASE_SUGGEST' | 'DID_YOU_MEAN' | 'UNDO_AUTO_CORRECT' | 'RELATED_SEARCHES'
= 'ORGANIC';
const query = new Query({
clusterUrl: 'http://localhost:31',
market: 'dk2',
locale: 'da-DK2',
touchpoint: 'mobile',
session: () => ({ customerKey: 'c31', sessionKey: 's31' })
});
const result = await query.searchPage({
q: 'robe',
sort,
origin,
limit: 20,
skip: 10,
notify: false,
viewId: 'production',
priceId: 'default',
channels: 'ONLINE|STORE',
stores: 'storeF',
selectedCategory: 'women',
templateId: 'list-template',
viewAllSecondary: true,
presentCustom: ['lorem', 'ipsum'],
presentPrices: ['VIP'],
facets: {
color: ['firebrick'],
brand: ['nike', 'adidas'],
price: { min: 10, max: 500 },
coolFactor: { min: 9001 },
length: { max: 180 }
}
}, {
navigation: { include: true },
primaryList: {
include: true,
productFilter: {
brand: 'Nike',
color: ['RED', 'GREEN'],
price: { min: 10, max: 100 }
},
productRules: 'rule incl newness 10d '
},
contentLists: [
{ id: 'jgf', limit: 11, algorithm: 'TOP_CONTENT', contentFilter: { content_key: 'ck12345' } }
]
});
result satisfies m.SearchPage;
result.q satisfies string;
result.primaryList satisfies m.PrimaryList;
result.didYouMean satisfies m.DidYouMean[];
result.autoCorrect satisfies m.AutoCorrect | undefined;
result.navigation satisfies m.Navigation | undefined;
result.contentLists satisfies m.ContentList[];
result.secondaryList satisfies m.SecondaryList | undefined;
result.relatedSearches satisfies m.RelatedSearch[] | undefined;
});
it(productPageSuite, 'should pass on correct endpoint, facets, params, and body parameters to Request', async () => {
const query = new Query({
clusterUrl: 'http://localhost:40',
market: 'no',
locale: 'no-NO',
touchpoint: 'desktop',
session: () => ({ customerKey: 'c40', sessionKey: 's40' })
});
const params = Object.freeze({
productKey: 'p123456',
viewId: 'production' as const
});
const body = Object.freeze({ productGroup: { include: true } });
await query.productPage(params, body);
const expectedUrl = new URL('http://localhost:40/api/storefront/v3/queries/product-page?market=no&customerKey=c40&sessionKey=s40&locale=no-NO&touchpoint=desktop&productKey=p123456&viewId=production');
const expectedBody = '{"productGroup":{"include":true}}';
assertSpyCalls(fetchSpy, 1);
assertSpyCallArgs(fetchSpy, 0, [
expectedUrl,
{ method: 'POST', body: expectedBody, headers: { 'Content-Type': 'text/plain' } }
]);
});
it(productPageSuite, 'should allow all API parameters and body options (kitchen sink)', async() => {
const query = new Query({
clusterUrl: 'http://localhost:41',
market: 'no2',
locale: 'no-NO2',
touchpoint: 'desktop',
session: () => ({ customerKey: 'c41', sessionKey: 's41' })
});
const result = await query.productPage({
productKey: 'pk1234567',
notify: false,
viewId: 'production',
priceId: 'default',
channels: 'STORE|ONLINE',
stores: '123|abc|xyz',
templateId: 'rec-list-template',
presentCustom: ['season', 'age'],
presentPrices: ['member']
}, {
productGroup: {
include: true
},
recommendationLists: [
{ id: 'top', label: 'Top sellers', limit: 8, algorithm: 'TOP_PRODUCTS', visualization: 'GRID', showMoreLink: '/top', params: { productKey: 'pk1234' } }
]
});
result satisfies m.ProductPage;
result.productGroup satisfies m.ProductGroup | undefined;
result.recommendationLists satisfies m.RecommendationList[];
});
it(cartPageSuite, 'should pass on correct endpoint, facets, params, and body parameters to Request', async () => {
const query = new Query({
clusterUrl: 'http://localhost:50',
market: 'fi',
locale: 'fi-FI',
touchpoint: 'desktop',
session: () => ({ customerKey: 'c50', sessionKey: 's50' })
});
const params = Object.freeze({
cart: ['p123456', 'p234567'],
viewId: 'production' as const
});
const body = Object.freeze({ cart: { include: true } });
await query.cartPage(params, body);
const expectedUrl = new URL('http://localhost:50/api/storefront/v3/queries/cart-page?market=fi&customerKey=c50&sessionKey=s50&locale=fi-FI&touchpoint=desktop&cart=p123456%7Cp234567&viewId=production');
const expectedBody = '{"cart":{"include":true}}';
assertSpyCalls(fetchSpy, 1);
assertSpyCallArgs(fetchSpy, 0, [
expectedUrl,
{ method: 'POST', body: expectedBody, headers: { 'Content-Type': 'text/plain' } }
]);
});
it(cartPageSuite, 'should allow all API parameters and body options (kitchen sink)', async() => {
const query = new Query({
clusterUrl: 'http://localhost:51',
market: 'fi2',
locale: 'fi-FI2',
touchpoint: 'desktop',
session: () => ({ customerKey: 'c51', sessionKey: 's51' })
});
const result = await query.cartPage({
cart: ['pk1234567', 'pk2345678'],
notify: true,
viewId: 'preview',
priceId: 'default',
channels: 'ONLINE',
stores: 'storeA|storeB',
templateId: 'rec-list-template',
presentCustom: ['red', 'blue'],
presentPrices: ['SEK', 'EURO']
}, {
cart: {
include: true
},
recommendationLists: [
{ id: 'cart', label: 'Cart recs', limit: 8, algorithm: 'CART', visualization: 'GRID', showMoreLink: '/top', params: { cart: ['pk1234'] } }
]
});
result satisfies m.CartPage;
result.cart satisfies m.ProductGroup[];
result.recommendationLists satisfies m.RecommendationList[];
});
it(landingPageSuite, 'should allow being called without parameters', async () => {
const query = new Query({
clusterUrl: 'http://localhost:60',
market: 'de',
locale: 'de-DE',
touchpoint: 'desktop',
session: () => ({ customerKey: 'c60', sessionKey: 's60' })
});
await query.landingPage();
const expectedUrl = new URL('http://localhost:60/api/storefront/v3/queries/landing-page?market=de&customerKey=c60&sessionKey=s60&locale=de-DE&touchpoint=desktop');
assertSpyCalls(fetchSpy, 1);
assertSpyCallArgs(fetchSpy, 0, [expectedUrl, { method: 'GET' }]);
});
it(landingPageSuite, 'should pass on correct endpoint, facets, params, and body parameters to Request', async () => {
const query = new Query({
clusterUrl: 'http://localhost:61',
market: 'de2',
locale: 'de-DE2',
touchpoint: 'desktop',
session: () => ({ customerKey: 'c61', sessionKey: 's61' })
});
const params = Object.freeze({
sort: 'RELEVANCE' as const,
limit: 11,
facets: {
color: ['steelblue'],
size: ['XL']
}
});
const body = Object.freeze({ primaryList: { include: true } });
await query.landingPage(params, body);
const expectedUrl = new URL('http://localhost:61/api/storefront/v3/queries/landing-page?market=de2&customerKey=c61&sessionKey=s61&locale=de-DE2&touchpoint=desktop&sort=RELEVANCE&limit=11&f.color=steelblue&f.size=XL');
const expectedBody = '{"primaryList":{"include":true}}';
assertSpyCalls(fetchSpy, 1);
assertSpyCallArgs(fetchSpy, 0, [
expectedUrl,
{ method: 'POST', body: expectedBody, headers: { 'Content-Type': 'text/plain' } }
]);
});
it(landingPageSuite, 'should allow all API parameters and body options (kitchen sink)', async() => {
const sort: 'RELEVANCE' | 'NEWEST_FIRST' | 'PRICE_ASCENDING' | 'PRICE_DESCENDING' | 'DISCOUNT' | 'RATING' = 'RELEVANCE';
const query = new Query({
clusterUrl: 'http://localhost:62',
market: 'de3',
locale: 'de-DE3',
touchpoint: 'desktop',
session: () => ({ customerKey: 'c62', sessionKey: 's62' })
});
const result = await query.landingPage({
sort,
limit: 20,
skip: 10,
q: 'red dress',
notify: false,
pageReference: '/women/jackets',
includeNavigation: true,
viewId: 'production',
priceId: 'default',
channels: 'ONLINE',
stores: 'se|de|no',
templateId: 'list-template',
presentCustom: ['cut'],
presentPrices: ['foo', 'bar'],
facets: {
color: ['firebrick'],
brand: ['nike', 'adidas'],
price: { min: 10, max: 500 },
coolFactor: { min: 9001 },
length: { max: 180 }
}
}, {
navigation: { include: true },
primaryList: {
include: true,
productFilter: {
brand: 'Nike',
color: ['RED', 'GREEN'],
price: { min: 10, max: 100 }
},
productRules: 'rule incl newness 10d '
},
recommendationLists: [
{ id: 'peronsal', label: 'For you', algorithm: 'PERSONAL', limit: 8, visualization: 'CAROUSEL' }
],
contentLists: [
{ id: 'blogs', limit: 3, algorithm: 'NEWEST_CONTENT', contentFilter: { type: 'blog' } }
]
});
result satisfies m.LandingPage;
result.primaryList satisfies m.PrimaryList | undefined;
result.recommendationLists satisfies m.RecommendationList[];
result.contentLists satisfies m.ContentList[];
result.navigation satisfies m.Navigation | undefined;
result.seo satisfies m.SearchEngineOptimization;
});
it(sponsoredPageSuite, 'should allow being called without parameters', async () => {
const query = new Query({
clusterUrl: 'http://localhost:82',
market: 'fr',
locale: 'fr-EN',
touchpoint: 'desktop',
session: () => ({ customerKey: 'c82', sessionKey: 's82' })
});
await query.sponsoredPage({ pageReference: '/' });
const expectedUrl = new URL('http://localhost:82/api/storefront/v3/queries/sponsored-page?market=fr&customerKey=c82&sessionKey=s82&locale=fr-EN&touchpoint=desktop&pageReference=%2F');
assertSpyCalls(fetchSpy, 1);
assertSpyCallArgs(fetchSpy, 0, [expectedUrl, { method: 'GET' }]);
});
// it(sponsoredPageSuite, 'should pass on correct endpoint, facets, params, and body parameters to Request', async () => {
// const query = new Query({
// clusterUrl: 'http://localhost:83',
// market: 'fr2',
// locale: 'fr-EN2',
// touchpoint: 'desktop',
// session: () => ({ customerKey: 'c83', sessionKey: 's83' })
// });
// const params = Object.freeze({
// sort: 'RELEVANCE' as const,
// limit: 11,
// facets: {
// color: ['steelblue'],
// size: ['XL']
// }
// });
// const body = Object.freeze({ primaryList: { include: true } });
// await query.sponsoredPage(params, body);
// const expectedUrl = new URL('http://localhost:61/api/storefront/v3/queries/landing-page?market=de2&customerKey=c61&sessionKey=s61&locale=de-DE2&touchpoint=desktop&sort=RELEVANCE&limit=11&f.color=steelblue&f.size=XL');
// const expectedBody = '{"primaryList":{"include":true}}';
// assertSpyCalls(fetchSpy, 1);
// assertSpyCallArgs(fetchSpy, 0, [
// expectedUrl,
// { method: 'POST', body: expectedBody, headers: { 'Content-Type': 'text/plain' } }
// ]);
// });
it(sponsoredPageSuite, 'should allow all API parameters and body options (kitchen sink)', async () => {
const query = new Query({
clusterUrl: 'http://localhost:84',
market: 'fr3',
locale: 'fr-EN3',
touchpoint: 'desktop',
session: () => ({ customerKey: 'c84', sessionKey: 's84' })
});
const result = await query.sponsoredPage({
notify: false,
pageReference: '/women/jackets/sponsored',
viewId: 'production',
templateId: 'sponsored-template'
});
result satisfies m.SponsoredPage;
result.sponsoredLists satisfies m.SponsoredList[];
});
it(contentInformationSuite, 'should pass on correct endpoint, and params, parameters to Request', async () => {
const query = new Query({
clusterUrl: 'http://localhost:70',
market: 'fr',
locale: 'fr-FR',
touchpoint: 'mobile',
session: () => ({ customerKey: 'c70', sessionKey: 's70' })
});
const params = Object.freeze({ contentKeys: ['ck1234', 'ck9876'] });
await query.contentInformation(params);
const expectedUrl = new URL('http://localhost:70/api/storefront/v3/queries/content-information?market=fr&customerKey=c70&sessionKey=s70&locale=fr-FR&touchpoint=mobile&contentKeys=ck1234%7Cck9876');
assertSpyCalls(fetchSpy, 1);
assertSpyCallArgs(fetchSpy, 0, [
expectedUrl,
{ method: 'GET' }
]);
});
it(contentInformationSuite, 'should allow all API parameters and body options (kitchen sink)', async () => {
const query = new Query({
clusterUrl: 'http://localhost:71',
market: 'fr2',
locale: 'fr-FR2',
touchpoint: 'mobile',
session: () => ({ customerKey: 'c71', sessionKey: 's71' })
});
const result = await query.contentInformation({
contentKeys: ['ck123456', 'ck234567'],
notify: false,
viewId: 'production'
});
result satisfies m.ContentInformation;
result.items satisfies m.ContentItem[];
});
it(contentSearchPageSuite, 'should pass on correct endpoint, facets, params, and body parameters to Request', async() => {
const query = new Query({
clusterUrl: 'http://localhost:80',
market: 'es',
locale: 'es-ES',
touchpoint: 'desktop',
session: () => ({ customerKey: 'c80', sessionKey: 's80' })
});
const params = Object.freeze({
q: 'sizeguide',
limit: 11
});
const body = Object.freeze({ primaryList: { contentFilter: { type: 'article' } } });
await query.contentSearchPage(params, body);
const expectedUrl = new URL('http://localhost:80/api/storefront/v3/queries/content-search-page?market=es&customerKey=c80&sessionKey=s80&locale=es-ES&touchpoint=desktop&q=sizeguide&limit=11');
const expectedBody = '{"primaryList":{"contentFilter":{"type":"article"}}}';
assertSpyCalls(fetchSpy, 1);
assertSpyCallArgs(fetchSpy, 0, [
expectedUrl,
{ method: 'POST', body: expectedBody, headers: { 'Content-Type': 'text/plain' } }
]);
});
it(contentSearchPageSuite, 'should allow all API parameters and body options (kitchen sink)', async() => {
const query = new Query({
clusterUrl: 'http://localhost:81',
market: 'es2',
locale: 'es-ES2',
touchpoint: 'mobile',
session: () => ({ customerKey: 'c81', sessionKey: 's81' })
});
const result = await query.contentSearchPage({
q: 'faq',
sort: 'RELEVANCE',
origin: 'UNDO_AUTO_CORRECT',
skip: 10,
limit: 30,
notify: false,
viewId: 'preview'
}, {
primaryList: {
contentFilter: {
type: 'article',
content_key: ['ck123', 'ck234', 'ck345'],
'custom.foo': 'bar'
}
}
});
result satisfies m.ContentSearchPage;
result.q satisfies string;
result.primaryList satisfies m.PrimaryContentList;
result.didYouMean satisfies m.DidYouMean[];
result.autoCorrect satisfies m.AutoCorrect | undefined;
});