Skip to content

Commit 699ffb1

Browse files
committed
Integrate reviewed QA pilot tests and cold CI prerequisites
1 parent 34bba4e commit 699ffb1

7 files changed

Lines changed: 136 additions & 4 deletions

File tree

‎apps/cli/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
"test:layer-upload:ministack": "bun scripts/packaging-archives/layer-upload-ministack-acceptance.ts",
6868
"test:packaging-archives": "bun test scripts/packaging-archives",
6969
"test:mcp-docs": "bun test src/commands/mcp",
70-
"test:mcp-production": "bun scripts/test-mcp-production-eval.ts",
70+
"test:mcp-production": "turbo run build:dev-artifacts --filter=@stacktape/cli && bun scripts/test-mcp-production-eval.ts",
7171
"test:mcp-smoke": "bun scripts/test-mcp-smoke.ts",
7272
"test:npm-declarations": "bun scripts/verify-npm-declarations.ts",
7373
"test:perf-tools": "bun test scripts/perf scripts/analyze-bundle.spec.ts",

‎docs/testing.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ journey.
1414
Use focused tests for self-contained rules with meaningful input variations, such as naming, pricing, parsing and
1515
redaction. For isolated changes, describe realistic failures and write the failing cases before changing the
1616
implementation. For a bug, reproduce the failure before fixing it when practical. Do not add a test for every function
17-
or duplicate the same assertion at every layer.
17+
or duplicate the same assertion at every layer. An artificial input can protect a real compatibility rule; check what a
18+
test detects before deleting it.
1819

1920
| What changed | Test at this boundary |
2021
| ---------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

‎packages/naming/src/compatibility-contracts.test.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ describe('migrated naming compatibility contracts', () => {
2525
expect({
2626
bucket: cfLogicalNames.bucket('uploads'),
2727
lambda: cfLogicalNames.lambda('api'),
28+
ecsService: cfLogicalNames.ecsService('web', false),
2829
blueGreenService: cfLogicalNames.ecsService('web', true),
2930
route: cfLogicalNames.httpApiRoute({ method: 'GET', path: '/users/{id}', stpResourceName: 'api' }),
3031
websocketApi: cfLogicalNames.websocketApi('realtime'),
@@ -41,6 +42,7 @@ describe('migrated naming compatibility contracts', () => {
4142
}).toEqual({
4243
bucket: 'UploadsBucket',
4344
lambda: 'ApiFunction',
45+
ecsService: 'WebService',
4446
blueGreenService: 'WebBlueGreenService',
4547
route: 'StpApiGetUsersIdRoute',
4648
websocketApi: 'RealtimeWebsocketApi',
@@ -131,6 +133,12 @@ describe('migrated naming compatibility contracts', () => {
131133
});
132134

133135
test('preserves helper Lambda physical names and truncation', () => {
136+
expect(helperLambdaAwsResourceNames.originRequestEdgeLambda('my-project', 'us-east-1')).toBe(
137+
'my-project-stpOReq-us-east-1'
138+
);
139+
expect(helperLambdaAwsResourceNames.originResponseEdgeLambda('my-project', 'us-east-1')).toBe(
140+
'my-project-stpORes-us-east-1'
141+
);
134142
expect(helperLambdaAwsResourceNames.originRequestEdgeLambda('a'.repeat(80), 'us-east-1')).toBe(
135143
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-294f10'
136144
);

‎packages/naming/src/resource-names.test.ts‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,31 @@
1-
import { describe, expect, test } from 'bun:test';
1+
import { beforeEach, describe, expect, test } from 'bun:test';
22
import { awsResourceNames } from './aws-resource-names';
33
import { buildResourceName, obfuscatedNamesStateHolder } from './resource-names';
44
import { shortHash } from './short-hash';
55

66
describe('resource names', () => {
7+
beforeEach(() => {
8+
// Keep the module-level CLI signal assertions independent of test order.
9+
obfuscatedNamesStateHolder.usingObfuscateNames = false;
10+
});
11+
712
test('keeps exact-limit names and applies the established SHAKE256 suffix above the limit', () => {
813
expect(buildResourceName({ proposedResourceName: 'abcdefghij', lengthLimit: 10 })).toBe('abcdefghij');
14+
expect(obfuscatedNamesStateHolder.usingObfuscateNames).toBe(false);
915
expect(buildResourceName({ proposedResourceName: 'abcdefghijk', lengthLimit: 10 })).toBe('abc-f6ea2e');
1016
expect(obfuscatedNamesStateHolder.usingObfuscateNames).toBe(true);
17+
expect(buildResourceName({ proposedResourceName: 'fits', lengthLimit: 10 })).toBe('fits');
18+
expect(obfuscatedNamesStateHolder.usingObfuscateNames).toBe(true);
19+
});
20+
21+
test('hashes the complete proposed name when truncating names with a shared prefix', () => {
22+
const first = buildResourceName({ proposedResourceName: 'prefix-shared-tail-one', lengthLimit: 18 });
23+
const second = buildResourceName({ proposedResourceName: 'prefix-shared-tail-two', lengthLimit: 18 });
24+
25+
expect(first).toBe('prefix-shar-b8a445');
26+
expect(second).toBe('prefix-shar-c5d85e');
27+
expect(first).toHaveLength(18);
28+
expect(second).toHaveLength(18);
1129
});
1230

1331
test('preserves the shared short hash algorithm', () => {
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { expect } from '@playwright/test';
2+
import { test } from './fixtures.ts';
3+
4+
test('the named dialog contains keyboard focus, skips disabled actions, and restores focus after Escape', async ({
5+
page
6+
}) => {
7+
await page.goto('/');
8+
const opener = page.getByRole('button', { name: 'Open example dialog' });
9+
await opener.click();
10+
11+
const dialog = page.getByRole('dialog', { name: 'Example dialog' });
12+
const close = dialog.getByRole('button', { name: 'Close dialog' });
13+
const name = dialog.getByRole('textbox', { name: 'Example name' });
14+
const done = dialog.getByRole('button', { name: 'Done' });
15+
const unavailable = dialog.getByRole('button', { name: 'Unavailable action' });
16+
await expect(dialog).toBeVisible();
17+
await expect(close).toBeFocused();
18+
await expect(unavailable).toBeDisabled();
19+
20+
await page.keyboard.press('Tab');
21+
await expect(name).toBeFocused();
22+
await page.keyboard.press('Tab');
23+
await expect(done).toBeFocused();
24+
await page.keyboard.press('Tab');
25+
await expect(close).toBeFocused();
26+
await page.keyboard.press('Shift+Tab');
27+
await expect(done).toBeFocused();
28+
29+
await unavailable.click({ force: true });
30+
await expect(page.getByRole('status', { name: 'Unavailable action count' })).toHaveText('0');
31+
await page.keyboard.press('Escape');
32+
await expect(dialog).toHaveCount(0);
33+
await expect(opener).toBeFocused();
34+
await expect(page.getByRole('status', { name: 'Close callback count' })).toHaveText('1');
35+
});
36+
37+
test('closing by control or backdrop restores the prior scroll state and focus', async ({ page }) => {
38+
await page.goto('/');
39+
await page.evaluate(() => {
40+
document.body.style.overflow = 'scroll';
41+
document.documentElement.style.overflow = 'clip';
42+
});
43+
const opener = page.getByRole('button', { name: 'Open example dialog' });
44+
const dialog = page.getByRole('dialog', { name: 'Example dialog' });
45+
46+
await opener.click();
47+
await expect(dialog).toBeVisible();
48+
await expect(page.locator('body')).toHaveCSS('overflow', 'hidden');
49+
await expect(page.locator('html')).toHaveCSS('overflow', 'hidden');
50+
await dialog.getByRole('button', { name: 'Close dialog' }).click();
51+
await expect(dialog).toHaveCount(0);
52+
await expect(opener).toBeFocused();
53+
await expect
54+
.poll(() => page.evaluate(() => [document.body.style.overflow, document.documentElement.style.overflow]))
55+
.toEqual(['scroll', 'clip']);
56+
await expect(page.getByRole('status', { name: 'Close callback count' })).toHaveText('1');
57+
58+
await opener.click();
59+
await expect(dialog).toBeVisible();
60+
await page.mouse.click(5, 5);
61+
await expect(dialog).toHaveCount(0);
62+
await expect(opener).toBeFocused();
63+
await expect
64+
.poll(() => page.evaluate(() => [document.body.style.overflow, document.documentElement.style.overflow]))
65+
.toEqual(['scroll', 'clip']);
66+
await expect(page.getByRole('status', { name: 'Close callback count' })).toHaveText('2');
67+
});
68+
69+
test('a protected dialog ignores overlay clicks but still closes with Escape', async ({ page }) => {
70+
await page.goto('/');
71+
const opener = page.getByRole('button', { name: 'Open protected dialog' });
72+
const dialog = page.getByRole('dialog', { name: 'Protected dialog' });
73+
await opener.click();
74+
await expect(dialog).toBeVisible();
75+
await page.mouse.click(5, 5);
76+
await expect(dialog).toBeVisible();
77+
await page.keyboard.press('Escape');
78+
await expect(dialog).toHaveCount(0);
79+
await expect(opener).toBeFocused();
80+
});

‎packages/ui-react/e2e/gallery/main.tsx‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,25 @@ import '../../src/styles.css';
66

77
function Gallery() {
88
const [open, setOpen] = useState(false);
9+
const [protectedOpen, setProtectedOpen] = useState(false);
10+
const [closeCount, setCloseCount] = useState(0);
11+
const [unavailableCount, setUnavailableCount] = useState(0);
912

1013
return (
1114
<main>
1215
<h1>Shared UI gallery</h1>
1316
<Button variant="primary" onClick={() => setOpen(true)}>
1417
Open example dialog
1518
</Button>
19+
<Button variant="secondary" onClick={() => setProtectedOpen(true)}>
20+
Open protected dialog
21+
</Button>
22+
<output aria-label="Close callback count">{closeCount}</output>
23+
<output aria-label="Unavailable action count">{unavailableCount}</output>
1624
<Dialog
1725
open={open}
1826
onOpenChange={setOpen}
27+
onClose={() => setCloseCount((count) => count + 1)}
1928
title="Example dialog"
2029
actions={
2130
<Button variant="secondary" onClick={() => setOpen(false)}>
@@ -26,10 +35,13 @@ function Gallery() {
2635
<p>This dialog uses the shared component and stylesheet with synthetic content.</p>
2736
<label htmlFor="example-name">Example name</label>
2837
<input id="example-name" defaultValue="Example project" />
29-
<Button variant="primary" disabled>
38+
<Button variant="primary" disabled onClick={() => setUnavailableCount((count) => count + 1)}>
3039
Unavailable action
3140
</Button>
3241
</Dialog>
42+
<Dialog open={protectedOpen} onOpenChange={setProtectedOpen} title="Protected dialog" closeOnOverlayClick={false}>
43+
<p>Clicking outside this dialog leaves it open.</p>
44+
</Dialog>
3345
</main>
3446
);
3547
}

‎scripts/workspace/test-plan.ts‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ const RULES: Rule[] = [
5050
],
5151
matches: (path) => path.startsWith('apps/cli/') && !path.includes('/starter-projects/')
5252
},
53+
{
54+
id: 'naming',
55+
proves: 'Stable resource names, logical IDs, hashes and their consumers preserve compatibility.',
56+
commands: ['pnpm --filter @stacktape/naming test', 'pnpm --filter @stacktape/naming typecheck'],
57+
matches: (path) => path.startsWith('packages/naming/')
58+
},
5359
{
5460
id: 'synthesis',
5561
proves: 'Config resolution and synthesized infrastructure preserve semantic contracts.',
@@ -98,6 +104,13 @@ const RULES: Rule[] = [
98104
path.startsWith('apps/console/api/prisma/') ||
99105
hasPart(path, /^apps\/console\/api\/src\/(raw-sql-queries|services\/prisma|model-helpers)/)
100106
},
107+
{
108+
id: 'shared-ui-browser',
109+
proves:
110+
'The shared Dialog and Button preserve keyboard, focus, dismissal and disabled behavior in an isolated synthetic browser app.',
111+
commands: ['pnpm --filter @stacktape/ui-react test:e2e'],
112+
matches: (path) => path.startsWith('packages/ui-react/')
113+
},
101114
{
102115
id: 'console-ui',
103116
proves: 'Console UI helpers compile and the production bundle is valid.',

0 commit comments

Comments
 (0)