import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; import { Tree, addProjectConfiguration, readProjectConfiguration, } from '@nx/devkit'; import * as utils from '@abgov/nx-oc'; import { environments } from '@abgov/nx-oc'; import angularApp from './angular-app'; import { AngularAppGeneratorSchema } from './schema'; jest.mock('@nx/devkit', () => ({ ...jest.requireActual('@nx/devkit'), formatFiles: jest.fn().mockResolvedValue(undefined), })); jest.mock('@abgov/nx-oc'); jest.mock('../../utils/agent', () => ({ consultAgent: jest.fn().mockResolvedValue(null), confirmAfterAgentInterrupt: jest.fn().mockResolvedValue(undefined), })); const utilsMock = utils as jest.Mocked; utilsMock.getAdspConfiguration.mockResolvedValue({ tenant: 'test', tenantRealm: 'test', accessServiceUrl: environments.test.accessServiceUrl, directoryServiceUrl: environments.test.directoryServiceUrl, }); // jest.mock('@abgov/nx-oc') automocks adspProjectTags too — restore the real // (pure, no I/O) implementation so tag-writing behavior is actually exercised. utilsMock.adspProjectTags.mockImplementation( jest.requireActual('@abgov/nx-oc').adspProjectTags, ); describe('angular app generator', () => { let appTree: Tree; const options: AngularAppGeneratorSchema = { name: 'test', env: 'dev', }; beforeEach(() => { appTree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); }); it('should run successfully', async () => { await angularApp(appTree, options); const config = readProjectConfiguration(appTree, 'test'); expect(config).toBeDefined(); expect(config.root).toBe('apps/test'); expect(appTree.exists('apps/test/nginx.conf')).toBeTruthy(); }, 120000); it('renders the sign-in button in the app header (needs the "utilities" slot)', async () => { // goab-app-header (v2) only renders content placed in a named slot — GoA's // own design system docs put account/sign-in actions in "utilities" (vs. // "navigation" for nav links). A bare child with no slot attribute // renders nothing, silently dropping the sign-in button. Regression // guard for exactly that gap. await angularApp(appTree, options); const appHtml = appTree .read('apps/test/src/app/app.component.html') .toString(); expect(appHtml).toMatch(/
[\s\S]* { await angularApp(appTree, options); expect(appTree.exists('.mcp.json')).toBeTruthy(); expect(appTree.exists('.vscode/settings.json')).toBeTruthy(); }, 120000); it('scaffolds a Playwright e2e project (consistent across frontends)', async () => { await angularApp(appTree, options); expect(appTree.exists('apps/test-e2e/project.json')).toBeTruthy(); expect( appTree.exists('apps/test-e2e/playwright.config.ts') || appTree.exists('apps/test-e2e/playwright.config.mts'), ).toBe(true); expect(appTree.exists('apps/test-e2e/cypress.config.ts')).toBeFalsy(); // webServer guarded so CI can target a deployed URL (BASE_URL) instead of a local server const cfg = appTree.exists('apps/test-e2e/playwright.config.mts') ? 'apps/test-e2e/playwright.config.mts' : 'apps/test-e2e/playwright.config.ts'; expect(appTree.read(cfg).toString()).toContain('process.env.BASE_URL'); // Accessibility check (axe-core, WCAG 2.1 A/AA) rides along in the same e2e project. expect(appTree.exists('apps/test-e2e/src/a11y.spec.ts')).toBeTruthy(); const pkgJson = JSON.parse(appTree.read('package.json').toString()); expect(pkgJson.devDependencies['@axe-core/playwright']).toBeTruthy(); }, 120000); it('AGENTS.md points at the current design system docs', async () => { await angularApp(appTree, options); const agents = appTree.read('apps/test/AGENTS.md').toString(); expect(agents).toContain('design.alberta.ca/components'); // guard against the retired ui-components.alberta.ca URL creeping back in expect(agents).not.toContain('ui-components.alberta.ca'); }, 120000); it('can add nginx proxy', async () => { const host = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); await angularApp(host, { ...options, proxy: { location: '/test/', proxyPass: 'http://test-service:3333/', }, }); const config = readProjectConfiguration(host, 'test'); expect(config.root).toBe('apps/test'); expect(host.exists('apps/test/nginx.conf')).toBeTruthy(); expect(host.read('apps/test/nginx.conf').toString()).toContain( 'http://test-service:3333/', ); }); it('can add multiple nginx proxy', async () => { const host = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); await angularApp(host, { ...options, proxy: [ { location: '/test/', proxyPass: 'http://test-service:3333/', }, { location: '/test2/', proxyPass: 'http://test-service2:3333/', }, ], }); const config = readProjectConfiguration(host, 'test'); expect(config.root).toBe('apps/test'); expect(host.exists('apps/test/nginx.conf')).toBeTruthy(); const nginxConf = host.read('apps/test/nginx.conf').toString(); expect(nginxConf).toContain('http://test-service:3333/'); expect(nginxConf).toContain('http://test-service2:3333/'); }); it('can add webpack dev server proxy', async () => { const host = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); await angularApp(host, { ...options, proxy: { location: '/test/', proxyPass: 'http://test-service:3333/api/', }, }); const config = readProjectConfiguration(host, 'test'); expect(config.root).toBe('apps/test'); expect(host.exists('apps/test/proxy.conf.json')).toBeTruthy(); const proxyConf = JSON.parse( host.read('apps/test/proxy.conf.json').toString(), ); expect(proxyConf['/test/'].target).toBe('http://localhost:3333'); expect(proxyConf['/test/'].pathRewrite['^/test/']).toBe('/api/'); }); it('derives the nginx/dev proxy and the sandbox tag from --pairedProject alone', async () => { addProjectConfiguration(appTree, 'test-service', { root: 'apps/test-service', }); await angularApp(appTree, { ...options, pairedProject: 'test-service' }); const nginxConf = appTree.read('apps/test/nginx.conf').toString(); expect(nginxConf).toContain('http://test-service:3333/test-service/'); const proxyConf = JSON.parse( appTree.read('apps/test/proxy.conf.json').toString(), ); expect(proxyConf['/api/'].target).toBe('http://localhost:3333'); const config = readProjectConfiguration(appTree, 'test'); expect(config.tags).toContain('adsp:proxy-service:test-service:3333'); }); it('throws when --pairedProject names a project that does not exist', async () => { await expect( angularApp(appTree, { ...options, pairedProject: 'no-such-service' }), ).rejects.toThrow(); }); it('throws when --pairedProject and an explicit --proxy collide on the same location', async () => { addProjectConfiguration(appTree, 'test-service', { root: 'apps/test-service', }); await expect( angularApp(appTree, { ...options, pairedProject: 'test-service', proxy: { location: '/api/', proxyPass: 'http://other:9000/' }, }), ).rejects.toThrow(/already derives a proxy/); }); });