Skip to content

Commit 004cc23

Browse files
arbrandesclaude
andcommitted
feat: make externalScripts configurable via env.config.js
Allow externalScripts to be overridden via the config object, following the same pattern used for loggingService, analyticsService, and authService. GoogleAnalyticsLoader remains the default. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 44cc024 commit 004cc23

2 files changed

Lines changed: 64 additions & 6 deletions

File tree

src/initialize.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ function applyOverrideHandlers(overrides) {
270270
* implementation to use.
271271
* @param {*} [options.authMiddleware=[]] An array of middleware to apply to http clients in the auth service.
272272
* @param {*} [options.externalScripts=[GoogleAnalyticsLoader]] An array of externalScripts.
273-
* By default added GoogleAnalyticsLoader.
273+
* Can also be configured via `externalScripts` in env.config.js.
274274
* @param {*} [options.requireAuthenticatedUser=false] If true, turns on automatic login
275275
* redirection for unauthenticated users. Defaults to false, meaning that by default the
276276
* application will allow anonymous/unauthenticated sessions.
@@ -308,10 +308,6 @@ export async function initialize({
308308
await runtimeConfig();
309309
publish(APP_CONFIG_INITIALIZED);
310310

311-
loadExternalScripts(externalScripts, {
312-
config: getConfig(),
313-
});
314-
315311
// This allows us to replace the implementations of the logging, analytics, and auth services
316312
// based on keys in the ConfigDocument. The JavaScript File Configuration method is the only
317313
// one capable of supplying an alternative implementation since it can import other modules.
@@ -320,6 +316,11 @@ export async function initialize({
320316
const loggingServiceImpl = getConfig().loggingService || loggingService;
321317
const analyticsServiceImpl = getConfig().analyticsService || analyticsService;
322318
const authServiceImpl = getConfig().authService || authService;
319+
const externalScriptsImpl = getConfig().externalScripts || externalScripts;
320+
321+
loadExternalScripts(externalScriptsImpl, {
322+
config: getConfig(),
323+
});
323324

324325
// Logging
325326
configureLogging(loggingServiceImpl, {

src/initialize.test.js

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
APP_READY,
1111
APP_INIT_ERROR,
1212
} from './constants';
13-
import { initialize } from './initialize';
13+
import { initialize, loadExternalScripts } from './initialize';
1414
import { subscribe } from './pubSub';
1515

1616
import {
@@ -352,6 +352,63 @@ describe('initialize', () => {
352352
expect(hydrateAuthenticatedUser).not.toHaveBeenCalled();
353353
expect(logError).not.toHaveBeenCalled();
354354
});
355+
356+
it('should load externalScripts from config when provided', async () => {
357+
const mockScript = jest.fn().mockImplementation(() => ({ loadScript: jest.fn() }));
358+
config.externalScripts = [mockScript];
359+
360+
await initialize({ messages: null });
361+
362+
expect(mockScript).toHaveBeenCalledWith({ config });
363+
delete config.externalScripts;
364+
});
365+
366+
it('should fall back to externalScripts parameter when config has none', async () => {
367+
const mockScript = jest.fn().mockImplementation(() => ({ loadScript: jest.fn() }));
368+
369+
await initialize({ messages: null, externalScripts: [mockScript] });
370+
371+
expect(mockScript).toHaveBeenCalledWith({ config });
372+
});
373+
374+
it('should load GoogleAnalyticsLoader by default', async () => {
375+
const { GoogleAnalyticsLoader: GALoader } = jest.requireActual('./scripts');
376+
const loadScriptSpy = jest.spyOn(GALoader.prototype, 'loadScript').mockImplementation(() => {});
377+
378+
await initialize({ messages: null });
379+
380+
expect(loadScriptSpy).toHaveBeenCalled();
381+
loadScriptSpy.mockRestore();
382+
});
383+
384+
it('should prefer config externalScripts over the parameter', async () => {
385+
const configScript = jest.fn().mockImplementation(() => ({ loadScript: jest.fn() }));
386+
const paramScript = jest.fn().mockImplementation(() => ({ loadScript: jest.fn() }));
387+
config.externalScripts = [configScript];
388+
389+
await initialize({ messages: null, externalScripts: [paramScript] });
390+
391+
expect(configScript).toHaveBeenCalled();
392+
expect(paramScript).not.toHaveBeenCalled();
393+
delete config.externalScripts;
394+
});
395+
});
396+
397+
describe('loadExternalScripts', () => {
398+
it('should instantiate each script with data and call loadScript', () => {
399+
const loadScript = jest.fn();
400+
const MockScript = jest.fn().mockImplementation(() => ({ loadScript }));
401+
const data = { config: { some: 'value' } };
402+
403+
loadExternalScripts([MockScript], data);
404+
405+
expect(MockScript).toHaveBeenCalledWith(data);
406+
expect(loadScript).toHaveBeenCalled();
407+
});
408+
409+
it('should handle an empty array', () => {
410+
expect(() => loadExternalScripts([], {})).not.toThrow();
411+
});
355412
});
356413

357414
describe('history', () => {

0 commit comments

Comments
 (0)