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
35 changes: 35 additions & 0 deletions src/internal/utils/__tests__/global-flags-ssr.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* @jest-environment node
*/
/* eslint-disable header/header */
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import * as globalFlags from '../global-flags';
import { awsuiGlobalFlagsSymbol, FlagsHolder } from '../global-flags';
const { getGlobalFlag } = globalFlags;

declare const global: typeof globalThis & FlagsHolder;

afterEach(() => {
delete global[awsuiGlobalFlagsSymbol];
});

describe('getGlobalFlag', () => {
test('ensure no window in this environment', () => {
expect(typeof window === 'undefined').toBe(true);
});

test('returns undefined if the global flags object does not exist', () => {
expect(getGlobalFlag('removeHighContrastHeader')).toBeUndefined();
});
test('returns undefined if the global flags object exists but the flag is not set', () => {
global[awsuiGlobalFlagsSymbol] = {};
expect(getGlobalFlag('removeHighContrastHeader')).toBeUndefined();
});
test('returns removeHighContrastHeader value when defined', () => {
global[awsuiGlobalFlagsSymbol] = { removeHighContrastHeader: false };
expect(getGlobalFlag('removeHighContrastHeader')).toBe(false);
global[awsuiGlobalFlagsSymbol].removeHighContrastHeader = true;
expect(getGlobalFlag('removeHighContrastHeader')).toBe(true);
});
});
29 changes: 11 additions & 18 deletions src/internal/utils/__tests__/global-flags.test.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,25 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import * as globalFlags from '../global-flags';
import { FlagsHolder, awsuiGlobalFlagsSymbol } from '../global-flags';
const { getGlobalFlag } = globalFlags;

const awsuiGlobalFlagsSymbol = Symbol.for('awsui-global-flags');
Copy link
Member Author

@just-boris just-boris Feb 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some unique symbol type madness here. Symbol.for(key) returns different symbols even if the key is the same: microsoft/TypeScript#35909

So I needed to add an import of awsuiGlobalFlagsSymbol. I also could not get it from const { ... } = globalFlags because it also would not be the same unique symbol.

But on the upside I managed to remove copies of these interfaces below while keeping everything type safe

interface GlobalFlags {
removeHighContrastHeader?: boolean;
}

interface ExtendedWindow extends Window {
[awsuiGlobalFlagsSymbol]?: GlobalFlags;
}
declare const window: ExtendedWindow;
declare const window: Window & FlagsHolder;

afterEach(() => {
delete window[awsuiGlobalFlagsSymbol];
jest.restoreAllMocks();
});

describe('getGlobalFlag', () => {
test('returns undefined if there global flags are not defined', () => {
test('returns undefined if the global flags object does not exist', () => {
expect(getGlobalFlag('removeHighContrastHeader')).toBeUndefined();
});
test('returns undefined if global flags are defined but flag is not set', () => {
test('returns undefined if the global flags object exists but the flag is not set', () => {
window[awsuiGlobalFlagsSymbol] = {};
expect(getGlobalFlag('removeHighContrastHeader')).toBeUndefined();
});
test('returns removeHighContrastHeader value', () => {
test('returns removeHighContrastHeader value when defined', () => {
window[awsuiGlobalFlagsSymbol] = { removeHighContrastHeader: false };
expect(getGlobalFlag('removeHighContrastHeader')).toBe(false);
window[awsuiGlobalFlagsSymbol].removeHighContrastHeader = true;
Expand All @@ -35,31 +28,31 @@ describe('getGlobalFlag', () => {
test('returns removeHighContrastHeader value when defined in top window', () => {
jest
.spyOn(globalFlags, 'getTopWindow')
.mockReturnValue({ [awsuiGlobalFlagsSymbol]: { removeHighContrastHeader: true } } as unknown as ExtendedWindow);
.mockReturnValue({ [awsuiGlobalFlagsSymbol]: { removeHighContrastHeader: true } } as typeof window);
expect(getGlobalFlag('removeHighContrastHeader')).toBe(true);
jest.restoreAllMocks();

jest
.spyOn(globalFlags, 'getTopWindow')
.mockReturnValue({ [awsuiGlobalFlagsSymbol]: { removeHighContrastHeader: false } } as unknown as ExtendedWindow);
.mockReturnValue({ [awsuiGlobalFlagsSymbol]: { removeHighContrastHeader: false } } as typeof window);
expect(getGlobalFlag('removeHighContrastHeader')).toBe(false);
});
test('privileges values in the self window', () => {
jest
.spyOn(globalFlags, 'getTopWindow')
.mockReturnValue({ [awsuiGlobalFlagsSymbol]: { removeHighContrastHeader: false } } as unknown as ExtendedWindow);
.mockReturnValue({ [awsuiGlobalFlagsSymbol]: { removeHighContrastHeader: false } } as typeof window);
window[awsuiGlobalFlagsSymbol] = { removeHighContrastHeader: true };
expect(getGlobalFlag('removeHighContrastHeader')).toBe(true);
});
test('returns top window value when not defined in the self window', () => {
jest
.spyOn(globalFlags, 'getTopWindow')
.mockReturnValue({ [awsuiGlobalFlagsSymbol]: { removeHighContrastHeader: true } } as unknown as ExtendedWindow);
.mockReturnValue({ [awsuiGlobalFlagsSymbol]: { removeHighContrastHeader: true } } as typeof window);
window[awsuiGlobalFlagsSymbol] = {};
expect(getGlobalFlag('removeHighContrastHeader')).toBe(true);
});
test('returns undefined when top window is undefined', () => {
jest.spyOn(globalFlags, 'getTopWindow').mockReturnValue(undefined);
test('returns undefined when top window is not available', () => {
jest.spyOn(globalFlags, 'getTopWindow').mockReturnValue(null);
expect(getGlobalFlag('removeHighContrastHeader')).toBeUndefined();
});
test('returns undefined when an error is thrown and flag is not defined in own window', () => {
Expand Down
21 changes: 11 additions & 10 deletions src/internal/utils/global-flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,26 @@ interface GlobalFlags {
removeHighContrastHeader?: boolean;
}

interface ExtendedWindow extends Window {
export interface FlagsHolder {
[awsuiGlobalFlagsSymbol]?: GlobalFlags;
}
declare const window: ExtendedWindow;

export const getTopWindow = (): ExtendedWindow | undefined => {
return window.top as ExtendedWindow;
export const getTopWindow = () => {
return window.top;
};

function readFlag(window: ExtendedWindow | undefined, flagName: keyof GlobalFlags) {
if (typeof window === 'undefined' || !window[awsuiGlobalFlagsSymbol]) {
return undefined;
}
return window[awsuiGlobalFlagsSymbol][flagName];
function getGlobal() {
return typeof window !== 'undefined' ? window : globalThis;
}

function readFlag(window: unknown, flagName: keyof GlobalFlags) {
const holder = window as FlagsHolder | null;
return holder?.[awsuiGlobalFlagsSymbol]?.[flagName];
}

export const getGlobalFlag = (flagName: keyof GlobalFlags): GlobalFlags[keyof GlobalFlags] | undefined => {
try {
const ownFlag = readFlag(window, flagName);
const ownFlag = readFlag(getGlobal(), flagName);
if (ownFlag !== undefined) {
return ownFlag;
}
Expand Down