Skip to content
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
31 changes: 31 additions & 0 deletions src/embed/search.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,37 @@ describe('Search embed tests', () => {
});
});

test('getUpdateEmbedParamsObject sends dataSources as a real ARRAY, not the URL-encoded string', async () => {
// The iframe URL correctly carries dataSources as a JSON-encoded
// string (asserted above), but the UpdateEmbedParams postMessage
// payload is spread into app state unparsed — a string there reaches
// the $sources GraphQL variable ([GUID!]) and fails GUID coercion
// (SCAL-334713, fired from beforePrerenderVisible on prerender-show).
const dataSources = ['4dd30af7-9ed7-4847-8f28-b65b44a841d8'];
const searchEmbed = new SearchEmbed(getRootEl(), {
...defaultViewConfig,
dataSources,
});
const params = await (searchEmbed as any).getUpdateEmbedParamsObject();
expect(params.dataSources).toEqual(dataSources);

// Single-GUID `dataSource` prop (the customer's shape) normalizes to
// a one-element array the same way the app's URL parser would.
const singleSourceEmbed = new SearchEmbed(getRootEl(), {
...defaultViewConfig,
dataSource: '4dd30af7-9ed7-4847-8f28-b65b44a841d8',
});
const singleParams = await (singleSourceEmbed as any).getUpdateEmbedParamsObject();
expect(singleParams.dataSources).toEqual([
'4dd30af7-9ed7-4847-8f28-b65b44a841d8',
]);

// The normalization only reverses JSON serialization: non-JSON
// strings and already-structured values pass through untouched.
expect(singleParams.embedApp).toBe(true);
expect(singleParams.authType).toBe('None');
});

test('should pass in search query', async () => {
const dataSources = ['data-source-1'];
const searchOptions = {
Expand Down
12 changes: 8 additions & 4 deletions src/embed/ts-embed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
isUndefined,
getHostEventsConfig,
getValueFromWindow,
deserializeParam,
} from '../utils';
import { getCustomActions } from '../utils/custom-actions';
import {
Expand Down Expand Up @@ -706,11 +707,14 @@ export class TsEmbed {
}

protected async getUpdateEmbedParamsObject() {
let queryParams = this.getEmbedParamsObject();
const queryParams = this.getEmbedParamsObject();
// Values are URL-serialized (e.g. dataSources as '["guid"]'); parse
// them back so the event payload matches a URL load (SCAL-334713).
Object.keys(queryParams).forEach((key) => {
queryParams[key] = deserializeParam(queryParams[key]);
});
const appInitData = await this.getAppInitData();
queryParams = { ...this.viewConfig, ...queryParams, ...appInitData };

return queryParams;
return { ...this.viewConfig, ...queryParams, ...appInitData };
}

/**
Expand Down
44 changes: 44 additions & 0 deletions src/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
getQueryParamString,
deserializeParam,
getFilterQuery,
getCssDimension,
getEncodedQueryParamsString,
Expand Down Expand Up @@ -348,7 +349,50 @@ describe('unit test for utils', () => {
});
});

describe('deserializeParam', () => {
// Inverse of URL param serialization — must mirror the app's URL parser.
test('parses a JSON-string array back to a real array (SCAL-334713)', () => {
expect(deserializeParam('["4dd30af7-9ed7-4847-8f28-b65b44a841d8"]')).toEqual([
'4dd30af7-9ed7-4847-8f28-b65b44a841d8',
]);
});

test('does not URI-decode strings — raw %xx content must survive', () => {
expect(deserializeParam('%5Bcommit%20date%5D%5Brevenue%5D')).toBe(
'%5Bcommit%20date%5D%5Brevenue%5D',
);
});

test('parses JSON objects and booleans', () => {
expect(deserializeParam('{"a":1}')).toEqual({ a: 1 });
expect(deserializeParam('true')).toBe(true);
});

test('keeps numeric strings as strings, matching the app URL parser', () => {
expect(deserializeParam('123')).toBe('123');
expect(deserializeParam('1.5')).toBe('1.5');
});

test('keeps plain non-JSON strings unchanged', () => {
expect(deserializeParam('local-host')).toBe('local-host');
expect(deserializeParam('AuthServerCookieless')).toBe('AuthServerCookieless');
});

test('keeps a malformed percent-sequence string as-is', () => {
expect(deserializeParam('100% legit')).toBe('100% legit');
});

test('passes non-string values through untouched', () => {
const arr = ['a'];
expect(deserializeParam(arr)).toBe(arr);
expect(deserializeParam(true)).toBe(true);
expect(deserializeParam(42)).toBe(42);
expect(deserializeParam(undefined)).toBeUndefined();
});
});

describe('Fullscreen Utility Functions', () => {

let originalExitFullscreen: any;
let mockIframe: HTMLIFrameElement;

Expand Down
16 changes: 16 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,22 @@ const serializeParam = (value: any) => {
return JSON.stringify(value);
};

/**
* Inverse of serializeParam: JSON.parse with fallback to the raw string,
* matching how the app parses the same values from the iframe URL.
*/
export const deserializeParam = (value: unknown): unknown => {
if (typeof value !== 'string') return value;
try {
const parsed = JSON.parse(value);
// Numeric strings stay strings ('123' !== 123), same as the app's
// URL parser — param consumers expect string ids/versions.
return typeof parsed === 'number' ? value : parsed;
} catch (e) {
return value;
}
};

/**
* Convert a value to a string:
* in case of an array, we convert it to CSV.
Expand Down
Loading