Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
53abbd3
feat: migrate Counter to Spartan.ng UI primitives (closes #189)
chrisjwalk-bot Jun 13, 2026
72edd6c
test: add specs for spartan lib directives and components
chrisjwalk-bot Jun 14, 2026
b709c7c
feat: migrate all remaining Material components to Spartan.ng (closes…
chrisjwalk-bot Jun 14, 2026
26cd5c1
chore: remove @angular/material dependency, fix type errors
chrisjwalk-bot Jun 14, 2026
07c4f43
style: polish Spartan UI to closer match Material design
chrisjwalk-bot Jun 14, 2026
46d7746
fix: use global CSS for tooltip styling in CDK overlay
chrisjwalk-bot Jun 14, 2026
3daf2f6
style: polish forecast table and paginator
chrisjwalk-bot Jun 14, 2026
eaefbd3
fix: use attribute selector fallback + !important for tooltip CSS
chrisjwalk-bot Jun 14, 2026
d51ecb8
style: match Material table density and polish paginator select
chrisjwalk-bot Jun 14, 2026
8a5292b
style: add Rows per page label to table paginator
chrisjwalk-bot Jun 14, 2026
f5b577c
style: remove borders — match Material no-line philosophy
chrisjwalk-bot Jun 14, 2026
59a6172
fix: add cursor-pointer to HlmButton base, disabled:cursor-default
chrisjwalk-bot Jun 14, 2026
e3e2f3f
fix: simplify tooltip CSS selector to [role='tooltip'] only
chrisjwalk-bot Jun 14, 2026
2c75c62
feat: add E2E visual regression tests and fix typography for Spartan …
chrisjwalk-bot Jun 19, 2026
e09828f
chore: update Material baselines from local main branch
chrisjwalk-bot Jun 19, 2026
2dae6e1
fix: add html,body height:100% to match Material layout
chrisjwalk-bot Jun 19, 2026
ac4be7f
fix: move font-size from html to body to preserve Tailwind rem scale
chrisjwalk-bot Jun 19, 2026
e8ef5d6
fix: match Material toolbar title size and sidenav link sizing
chrisjwalk-bot Jun 19, 2026
292d351
fix: remove global body font-size override
chrisjwalk-bot Jun 19, 2026
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
88 changes: 1 addition & 87 deletions apps/counter-remote/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const sharedDeps = {
requiredVersion: angVer,
},
'@angular/router': { singleton: true, requiredVersion: angVer },
// Angular CDK/Material sub-paths — import:false so the remote uses the host's
// Angular CDK sub-paths — import:false so the remote uses the host's
// already-evaluated module instance rather than loading a second copy from its
// own dev server. A top-level `import * as __mfLocalShare` in the generated
// loadShare virtual module would otherwise cause Angular to register the same
Expand Down Expand Up @@ -77,92 +77,6 @@ const sharedDeps = {
requiredVersion: cdkMatVer,
import: false,
},
// Angular Material sub-paths — same import:false reason as CDK above
'@angular/material/badge': {
singleton: true,
requiredVersion: cdkMatVer,
import: false,
},
'@angular/material/bottom-sheet': {
singleton: true,
requiredVersion: cdkMatVer,
import: false,
},
'@angular/material/button': {
singleton: true,
requiredVersion: cdkMatVer,
import: false,
},
'@angular/material/checkbox': {
singleton: true,
requiredVersion: cdkMatVer,
import: false,
},
'@angular/material/core': {
singleton: true,
requiredVersion: cdkMatVer,
import: false,
},
'@angular/material/form-field': {
singleton: true,
requiredVersion: cdkMatVer,
import: false,
},
'@angular/material/divider': {
singleton: true,
requiredVersion: cdkMatVer,
import: false,
},
'@angular/material/icon': {
singleton: true,
requiredVersion: cdkMatVer,
import: false,
},
'@angular/material/input': {
singleton: true,
requiredVersion: cdkMatVer,
import: false,
},
'@angular/material/list': {
singleton: true,
requiredVersion: cdkMatVer,
import: false,
},
'@angular/material/paginator': {
singleton: true,
requiredVersion: cdkMatVer,
import: false,
},
'@angular/material/progress-spinner': {
singleton: true,
requiredVersion: cdkMatVer,
import: false,
},
'@angular/material/sidenav': {
singleton: true,
requiredVersion: cdkMatVer,
import: false,
},
'@angular/material/snack-bar': {
singleton: true,
requiredVersion: cdkMatVer,
import: false,
},
'@angular/material/table': {
singleton: true,
requiredVersion: cdkMatVer,
import: false,
},
'@angular/material/toolbar': {
singleton: true,
requiredVersion: cdkMatVer,
import: false,
},
'@angular/material/tooltip': {
singleton: true,
requiredVersion: cdkMatVer,
import: false,
},
// NgRx - base signals shared (host provides, remote uses import:false).
// @ngrx/signals/events is NOT shared - the federation plugin generates
// buggy loadShare imports for sub-path modules. It's bundled directly.
Expand Down
1 change: 1 addition & 0 deletions apps/web-app-e2e/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export default defineConfig({
baseURL,
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
/* Run your local dev server before starting the tests */
webServer: [
Expand Down
52 changes: 52 additions & 0 deletions apps/web-app-e2e/playwright.remote.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { defineConfig, devices } from '@playwright/test';
import { nxE2EPreset } from '@nx/playwright/preset';

/**
* Remote E2E config — runs tests against a deployed environment.
*
* Use to capture baselines from the Material (main) deployment,
* then compare against the Spartan (local) build.
*
* Workflow for visual regression:
* 1. Capture Material baselines from Azure:
* BASE_URL=https://angularclinetcorengrxstarter.azurewebsites.net pnpm e2e:remote --update-snapshots
*
* 2. Run same tests against local Spartan build to compare:
* pnpm e2e
*
* 3. Or capture Spartan baselines from a preview deployment:
* BASE_URL=<preview-url> pnpm e2e:remote
*
* 4. Compare the two screenshot directories manually:
* diff apps/web-app-e2e/screenshots-material/ apps/web-app-e2e/src/__screenshots__/
*/
const baseURL =
(process.env['BASE_URL'] as string | undefined) ||
'https://angularclinetcorengrxstarter.azurewebsites.net';

export default defineConfig({
...nxE2EPreset(__filename, { testDir: './src' }),

use: {
baseURL,
trace: 'on-first-retry',
// Screenshots from the remote Material deployment go to a separate dir
// so they don't clash with local Spartan baselines.
screenshot: 'only-on-failure',
},

// Remote testing: no local dev servers to spin up.
// Override the webServer to be empty (the preset may set one).
webServer: [],

// Store Material baselines in a separate directory so they survive
// branch switches and don't conflict with local Spartan baselines.
snapshotDir: './screenshots-material',

projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
});
10 changes: 9 additions & 1 deletion apps/web-app-e2e/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,13 @@
"sourceRoot": "apps/web-app-e2e/src",
"implicitDependencies": ["web-app"],
"// targets": "to see all targets run: nx show project web-app-e2e --web",
"targets": {}
"targets": {
"e2e-remote": {
"executor": "nx:run-commands",
"options": {
"cwd": "apps/web-app-e2e",
"command": "npx playwright test --config playwright.remote.config.ts"
}
}
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions apps/web-app-e2e/src/content.e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,4 +158,18 @@ test.describe('Content page', () => {
const mermaidLink = toc.getByRole('link', { name: /mermaid diagrams/i });
await expect(mermaidLink).toHaveClass(/font-medium/, { timeout: 3_000 });
});

test(
'visual snapshot',
{ tag: '@visual', timeout: 60_000 },
async ({ page }) => {
await page.goto('/content');
await expect(page.getByTestId('app-content')).toBeVisible({
timeout: 60_000,
});
// Wait for mermaid SVGs to render
await page.locator('.mermaid svg').first().waitFor({ timeout: 10_000 });
await expect(page).toHaveScreenshot('content.png', { fullPage: true });
},
);
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions apps/web-app-e2e/src/counter.e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,10 @@ test.describe('Counter Feature E2E', () => {
await decrementButton.click();
await expect(countLocator).toHaveText(String(initial));
});

test('visual snapshot', { tag: '@visual' }, async ({ page }) => {
await page.goto('/mfe-counter');
await expect(page.locator('[data-testid="lib-counter"]')).toBeVisible();
await expect(page).toHaveScreenshot('counter.png', { fullPage: true });
});
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
91 changes: 91 additions & 0 deletions apps/web-app-e2e/src/debug.e2e.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { expect, test } from '@playwright/test';

test.describe('Debug page', () => {
test('should load the debug page', async ({ page }) => {
await page.goto('/debug');

await expect(
page.getByRole('heading', { name: /debug tools/i }),
).toBeVisible();
});

test('should display notification action buttons', async ({ page }) => {
await page.goto('/debug');

await expect(page.getByRole('button', { name: /add info/i })).toBeVisible();
await expect(
page.getByRole('button', { name: /add error/i }),
).toBeVisible();
await expect(
page.getByRole('button', { name: /add sw-update/i }),
).toBeVisible();
});

test('should add an info notification when clicking Add info', async ({
page,
}) => {
await page.goto('/debug');

// Click the "Add info" button
await page.getByRole('button', { name: /add info/i }).click();

// The notification bell badge should update
const bell = page.locator('[data-testid="lib-notification-bell"]');
await expect(bell.locator('button')).toHaveAttribute(
'aria-label',
'1 unread notification',
{ timeout: 3000 },
);
});

test('should add multiple notifications', async ({ page }) => {
await page.goto('/debug');

// Add an info and an error notification
await page.getByRole('button', { name: /add info/i }).click();
await page.getByRole('button', { name: /add error/i }).click();

// Badge should show 2 unread
const bell = page.locator('[data-testid="lib-notification-bell"]');
await expect(bell.locator('button')).toHaveAttribute(
'aria-label',
'2 unread notifications',
{ timeout: 3000 },
);

// Open the notification panel
await bell.locator('button').click();

const panel = page.locator('[data-testid="lib-notification-list"]');
await expect(panel).toBeVisible({ timeout: 5000 });
await expect(panel.locator('text=Info notification')).toBeVisible();
await expect(panel.locator('text=Something went wrong')).toBeVisible();
});

test('should clear all notifications', async ({ page }) => {
await page.goto('/debug');

// Add a couple of notifications
await page.getByRole('button', { name: /add info/i }).click();
await page.getByRole('button', { name: /add error/i }).click();

// Click "Clear all"
await page.getByRole('button', { name: /clear all/i }).click();

// Badge should disappear
const bell = page.locator('[data-testid="lib-notification-bell"]');
await expect(bell.locator('button')).toHaveAttribute(
'aria-label',
'Notifications',
{ timeout: 3000 },
);
});

test('visual snapshot', { tag: '@visual' }, async ({ page }) => {
await page.goto('/debug');
await expect(
page.getByRole('heading', { name: /debug tools/i }),
).toBeVisible();
await expect(page).toHaveScreenshot('debug.png', { fullPage: true });
});
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions apps/web-app-e2e/src/home.e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,10 @@ test.describe('Home page', () => {
page.getByRole('heading', { name: /nx · angular · \.net/i }),
).toBeVisible();
});

test('visual snapshot', { tag: '@visual' }, async ({ page }) => {
await page.goto('/');
await expect(page.getByTestId('lib-home')).toBeVisible();
await expect(page).toHaveScreenshot('home.png', { fullPage: true });
});
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions apps/web-app-e2e/src/login.e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,10 @@ test.describe('Login page', () => {

await expect(page.getByRole('button', { name: /sign in/i })).toBeEnabled();
});

test('visual snapshot', { tag: '@visual' }, async ({ page }) => {
await page.goto('/login');
await expect(page.getByTestId('lib-login')).toBeVisible();
await expect(page).toHaveScreenshot('login.png', { fullPage: true });
});
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions apps/web-app-e2e/src/mfe-integration.e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,12 @@ test.describe('MFE Integration', () => {

expect(errors).toHaveLength(0);
});

test('visual snapshot', { tag: '@visual' }, async ({ page }) => {
await page.goto('/mfe-counter');
await expect(page.locator('[data-testid="lib-counter"]')).toBeVisible({
timeout: 15000,
});
await expect(page).toHaveScreenshot('mfe-counter.png', { fullPage: true });
});
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
32 changes: 32 additions & 0 deletions apps/web-app-e2e/src/navigation.e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ test.describe('Navigation', () => {
await expect(page).toHaveURL(/\/$/);
await expect(page.getByTestId('lib-home')).toBeVisible();
});

test('visual snapshot (desktop)', { tag: '@visual' }, async ({ page }) => {
await page.goto('/');
await expect(page.getByTestId('lib-home')).toBeVisible();
await expect(page).toHaveScreenshot('navigation-desktop.png', {
fullPage: true,
});
});
});

test.describe('sidenav (mobile)', () => {
Expand Down Expand Up @@ -64,5 +72,29 @@ test.describe('Navigation', () => {

await expect(page).toHaveURL(/\/mfe-counter/);
});

test('visual snapshot (mobile)', { tag: '@visual' }, async ({ page }) => {
await page.goto('/');
await expect(page.getByTestId('lib-home')).toBeVisible();
await expect(page).toHaveScreenshot('navigation-mobile.png', {
fullPage: true,
});
});

test(
'visual snapshot (mobile sidenav open)',
{ tag: '@visual' },
async ({ page }) => {
await page.goto('/');
await page.getByRole('button', { name: 'Toggle side menu' }).click();
await expect(page.getByTestId('lib-sidenav')).toBeVisible();
await expect(page).toHaveScreenshot(
'navigation-mobile-sidenav-open.png',
{
fullPage: true,
},
);
},
);
});
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading