@runchr/gstack-antigravity 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of @runchr/gstack-antigravity might be problematic. Click here for more details.
- package/.agents/skills/gstack/.agents/skills/gstack/SKILL.md +651 -0
- package/.agents/skills/gstack/.agents/skills/gstack-autoplan/SKILL.md +678 -0
- package/.agents/skills/gstack/.agents/skills/gstack-benchmark/SKILL.md +482 -0
- package/.agents/skills/gstack/.agents/skills/gstack-browse/SKILL.md +511 -0
- package/.agents/skills/gstack/.agents/skills/gstack-canary/SKILL.md +486 -0
- package/.agents/skills/gstack/.agents/skills/gstack-careful/SKILL.md +50 -0
- package/.agents/skills/gstack/.agents/skills/gstack-cso/SKILL.md +607 -0
- package/.agents/skills/gstack/.agents/skills/gstack-design-consultation/SKILL.md +615 -0
- package/.agents/skills/gstack/.agents/skills/gstack-design-review/SKILL.md +988 -0
- package/.agents/skills/gstack/.agents/skills/gstack-document-release/SKILL.md +604 -0
- package/.agents/skills/gstack/.agents/skills/gstack-freeze/SKILL.md +67 -0
- package/.agents/skills/gstack/.agents/skills/gstack-guard/SKILL.md +62 -0
- package/.agents/skills/gstack/.agents/skills/gstack-investigate/SKILL.md +415 -0
- package/.agents/skills/gstack/.agents/skills/gstack-land-and-deploy/SKILL.md +873 -0
- package/.agents/skills/gstack/.agents/skills/gstack-office-hours/SKILL.md +986 -0
- package/.agents/skills/gstack/.agents/skills/gstack-plan-ceo-review/SKILL.md +1268 -0
- package/.agents/skills/gstack/.agents/skills/gstack-plan-design-review/SKILL.md +668 -0
- package/.agents/skills/gstack/.agents/skills/gstack-plan-eng-review/SKILL.md +826 -0
- package/.agents/skills/gstack/.agents/skills/gstack-qa/SKILL.md +1006 -0
- package/.agents/skills/gstack/.agents/skills/gstack-qa-only/SKILL.md +626 -0
- package/.agents/skills/gstack/.agents/skills/gstack-retro/SKILL.md +1065 -0
- package/.agents/skills/gstack/.agents/skills/gstack-review/SKILL.md +704 -0
- package/.agents/skills/gstack/.agents/skills/gstack-setup-browser-cookies/SKILL.md +325 -0
- package/.agents/skills/gstack/.agents/skills/gstack-setup-deploy/SKILL.md +450 -0
- package/.agents/skills/gstack/.agents/skills/gstack-ship/SKILL.md +1312 -0
- package/.agents/skills/gstack/.agents/skills/gstack-unfreeze/SKILL.md +36 -0
- package/.agents/skills/gstack/.agents/skills/gstack-upgrade/SKILL.md +220 -0
- package/.agents/skills/gstack/.env.example +5 -0
- package/.agents/skills/gstack/.github/workflows/skill-docs.yml +17 -0
- package/.agents/skills/gstack/AGENTS.md +49 -0
- package/.agents/skills/gstack/ARCHITECTURE.md +359 -0
- package/.agents/skills/gstack/BROWSER.md +271 -0
- package/.agents/skills/gstack/CHANGELOG.md +800 -0
- package/.agents/skills/gstack/CLAUDE.md +284 -0
- package/.agents/skills/gstack/CONTRIBUTING.md +370 -0
- package/.agents/skills/gstack/ETHOS.md +129 -0
- package/.agents/skills/gstack/LICENSE +21 -0
- package/.agents/skills/gstack/README.md +228 -0
- package/.agents/skills/gstack/SKILL.md +657 -0
- package/.agents/skills/gstack/SKILL.md.tmpl +281 -0
- package/.agents/skills/gstack/TODOS.md +564 -0
- package/.agents/skills/gstack/VERSION +1 -0
- package/.agents/skills/gstack/autoplan/SKILL.md +689 -0
- package/.agents/skills/gstack/autoplan/SKILL.md.tmpl +416 -0
- package/.agents/skills/gstack/benchmark/SKILL.md +489 -0
- package/.agents/skills/gstack/benchmark/SKILL.md.tmpl +233 -0
- package/.agents/skills/gstack/bin/dev-setup +68 -0
- package/.agents/skills/gstack/bin/dev-teardown +56 -0
- package/.agents/skills/gstack/bin/gstack-analytics +191 -0
- package/.agents/skills/gstack/bin/gstack-community-dashboard +113 -0
- package/.agents/skills/gstack/bin/gstack-config +38 -0
- package/.agents/skills/gstack/bin/gstack-diff-scope +71 -0
- package/.agents/skills/gstack/bin/gstack-global-discover.ts +591 -0
- package/.agents/skills/gstack/bin/gstack-repo-mode +93 -0
- package/.agents/skills/gstack/bin/gstack-review-log +9 -0
- package/.agents/skills/gstack/bin/gstack-review-read +12 -0
- package/.agents/skills/gstack/bin/gstack-slug +15 -0
- package/.agents/skills/gstack/bin/gstack-telemetry-log +158 -0
- package/.agents/skills/gstack/bin/gstack-telemetry-sync +127 -0
- package/.agents/skills/gstack/bin/gstack-update-check +196 -0
- package/.agents/skills/gstack/browse/SKILL.md +517 -0
- package/.agents/skills/gstack/browse/SKILL.md.tmpl +141 -0
- package/.agents/skills/gstack/browse/bin/find-browse +21 -0
- package/.agents/skills/gstack/browse/bin/remote-slug +14 -0
- package/.agents/skills/gstack/browse/scripts/build-node-server.sh +48 -0
- package/.agents/skills/gstack/browse/src/browser-manager.ts +634 -0
- package/.agents/skills/gstack/browse/src/buffers.ts +137 -0
- package/.agents/skills/gstack/browse/src/bun-polyfill.cjs +109 -0
- package/.agents/skills/gstack/browse/src/cli.ts +420 -0
- package/.agents/skills/gstack/browse/src/commands.ts +111 -0
- package/.agents/skills/gstack/browse/src/config.ts +150 -0
- package/.agents/skills/gstack/browse/src/cookie-import-browser.ts +417 -0
- package/.agents/skills/gstack/browse/src/cookie-picker-routes.ts +207 -0
- package/.agents/skills/gstack/browse/src/cookie-picker-ui.ts +541 -0
- package/.agents/skills/gstack/browse/src/find-browse.ts +61 -0
- package/.agents/skills/gstack/browse/src/meta-commands.ts +269 -0
- package/.agents/skills/gstack/browse/src/platform.ts +17 -0
- package/.agents/skills/gstack/browse/src/read-commands.ts +335 -0
- package/.agents/skills/gstack/browse/src/server.ts +369 -0
- package/.agents/skills/gstack/browse/src/snapshot.ts +398 -0
- package/.agents/skills/gstack/browse/src/url-validation.ts +91 -0
- package/.agents/skills/gstack/browse/src/write-commands.ts +352 -0
- package/.agents/skills/gstack/browse/test/bun-polyfill.test.ts +72 -0
- package/.agents/skills/gstack/browse/test/commands.test.ts +1836 -0
- package/.agents/skills/gstack/browse/test/config.test.ts +250 -0
- package/.agents/skills/gstack/browse/test/cookie-import-browser.test.ts +397 -0
- package/.agents/skills/gstack/browse/test/cookie-picker-routes.test.ts +205 -0
- package/.agents/skills/gstack/browse/test/find-browse.test.ts +50 -0
- package/.agents/skills/gstack/browse/test/fixtures/basic.html +33 -0
- package/.agents/skills/gstack/browse/test/fixtures/cursor-interactive.html +22 -0
- package/.agents/skills/gstack/browse/test/fixtures/dialog.html +15 -0
- package/.agents/skills/gstack/browse/test/fixtures/empty.html +2 -0
- package/.agents/skills/gstack/browse/test/fixtures/forms.html +55 -0
- package/.agents/skills/gstack/browse/test/fixtures/qa-eval-checkout.html +108 -0
- package/.agents/skills/gstack/browse/test/fixtures/qa-eval-spa.html +98 -0
- package/.agents/skills/gstack/browse/test/fixtures/qa-eval.html +51 -0
- package/.agents/skills/gstack/browse/test/fixtures/responsive.html +49 -0
- package/.agents/skills/gstack/browse/test/fixtures/snapshot.html +55 -0
- package/.agents/skills/gstack/browse/test/fixtures/spa.html +24 -0
- package/.agents/skills/gstack/browse/test/fixtures/states.html +17 -0
- package/.agents/skills/gstack/browse/test/fixtures/upload.html +25 -0
- package/.agents/skills/gstack/browse/test/gstack-config.test.ts +125 -0
- package/.agents/skills/gstack/browse/test/gstack-update-check.test.ts +467 -0
- package/.agents/skills/gstack/browse/test/handoff.test.ts +235 -0
- package/.agents/skills/gstack/browse/test/path-validation.test.ts +63 -0
- package/.agents/skills/gstack/browse/test/platform.test.ts +37 -0
- package/.agents/skills/gstack/browse/test/snapshot.test.ts +467 -0
- package/.agents/skills/gstack/browse/test/test-server.ts +57 -0
- package/.agents/skills/gstack/browse/test/url-validation.test.ts +72 -0
- package/.agents/skills/gstack/canary/SKILL.md +493 -0
- package/.agents/skills/gstack/canary/SKILL.md.tmpl +220 -0
- package/.agents/skills/gstack/careful/SKILL.md +59 -0
- package/.agents/skills/gstack/careful/SKILL.md.tmpl +57 -0
- package/.agents/skills/gstack/careful/bin/check-careful.sh +112 -0
- package/.agents/skills/gstack/codex/SKILL.md +677 -0
- package/.agents/skills/gstack/codex/SKILL.md.tmpl +356 -0
- package/.agents/skills/gstack/conductor.json +6 -0
- package/.agents/skills/gstack/cso/SKILL.md +615 -0
- package/.agents/skills/gstack/cso/SKILL.md.tmpl +376 -0
- package/.agents/skills/gstack/design-consultation/SKILL.md +625 -0
- package/.agents/skills/gstack/design-consultation/SKILL.md.tmpl +369 -0
- package/.agents/skills/gstack/design-review/SKILL.md +998 -0
- package/.agents/skills/gstack/design-review/SKILL.md.tmpl +262 -0
- package/.agents/skills/gstack/docs/images/github-2013.png +0 -0
- package/.agents/skills/gstack/docs/images/github-2026.png +0 -0
- package/.agents/skills/gstack/docs/skills.md +877 -0
- package/.agents/skills/gstack/document-release/SKILL.md +613 -0
- package/.agents/skills/gstack/document-release/SKILL.md.tmpl +357 -0
- package/.agents/skills/gstack/freeze/SKILL.md +82 -0
- package/.agents/skills/gstack/freeze/SKILL.md.tmpl +80 -0
- package/.agents/skills/gstack/freeze/bin/check-freeze.sh +68 -0
- package/.agents/skills/gstack/gstack-upgrade/SKILL.md +226 -0
- package/.agents/skills/gstack/gstack-upgrade/SKILL.md.tmpl +224 -0
- package/.agents/skills/gstack/guard/SKILL.md +82 -0
- package/.agents/skills/gstack/guard/SKILL.md.tmpl +80 -0
- package/.agents/skills/gstack/investigate/SKILL.md +435 -0
- package/.agents/skills/gstack/investigate/SKILL.md.tmpl +196 -0
- package/.agents/skills/gstack/land-and-deploy/SKILL.md +880 -0
- package/.agents/skills/gstack/land-and-deploy/SKILL.md.tmpl +575 -0
- package/.agents/skills/gstack/office-hours/SKILL.md +996 -0
- package/.agents/skills/gstack/office-hours/SKILL.md.tmpl +624 -0
- package/.agents/skills/gstack/package.json +55 -0
- package/.agents/skills/gstack/plan-ceo-review/SKILL.md +1277 -0
- package/.agents/skills/gstack/plan-ceo-review/SKILL.md.tmpl +838 -0
- package/.agents/skills/gstack/plan-design-review/SKILL.md +676 -0
- package/.agents/skills/gstack/plan-design-review/SKILL.md.tmpl +314 -0
- package/.agents/skills/gstack/plan-eng-review/SKILL.md +836 -0
- package/.agents/skills/gstack/plan-eng-review/SKILL.md.tmpl +279 -0
- package/.agents/skills/gstack/qa/SKILL.md +1016 -0
- package/.agents/skills/gstack/qa/SKILL.md.tmpl +316 -0
- package/.agents/skills/gstack/qa/references/issue-taxonomy.md +85 -0
- package/.agents/skills/gstack/qa/templates/qa-report-template.md +126 -0
- package/.agents/skills/gstack/qa-only/SKILL.md +633 -0
- package/.agents/skills/gstack/qa-only/SKILL.md.tmpl +101 -0
- package/.agents/skills/gstack/retro/SKILL.md +1072 -0
- package/.agents/skills/gstack/retro/SKILL.md.tmpl +833 -0
- package/.agents/skills/gstack/review/SKILL.md +849 -0
- package/.agents/skills/gstack/review/SKILL.md.tmpl +259 -0
- package/.agents/skills/gstack/review/TODOS-format.md +62 -0
- package/.agents/skills/gstack/review/checklist.md +190 -0
- package/.agents/skills/gstack/review/design-checklist.md +132 -0
- package/.agents/skills/gstack/review/greptile-triage.md +220 -0
- package/.agents/skills/gstack/scripts/analytics.ts +190 -0
- package/.agents/skills/gstack/scripts/dev-skill.ts +82 -0
- package/.agents/skills/gstack/scripts/eval-compare.ts +96 -0
- package/.agents/skills/gstack/scripts/eval-list.ts +116 -0
- package/.agents/skills/gstack/scripts/eval-select.ts +86 -0
- package/.agents/skills/gstack/scripts/eval-summary.ts +187 -0
- package/.agents/skills/gstack/scripts/eval-watch.ts +172 -0
- package/.agents/skills/gstack/scripts/gen-skill-docs.ts +2414 -0
- package/.agents/skills/gstack/scripts/skill-check.ts +167 -0
- package/.agents/skills/gstack/setup +269 -0
- package/.agents/skills/gstack/setup-browser-cookies/SKILL.md +330 -0
- package/.agents/skills/gstack/setup-browser-cookies/SKILL.md.tmpl +74 -0
- package/.agents/skills/gstack/setup-deploy/SKILL.md +459 -0
- package/.agents/skills/gstack/setup-deploy/SKILL.md.tmpl +220 -0
- package/.agents/skills/gstack/ship/SKILL.md +1457 -0
- package/.agents/skills/gstack/ship/SKILL.md.tmpl +528 -0
- package/.agents/skills/gstack/supabase/config.sh +10 -0
- package/.agents/skills/gstack/supabase/functions/community-pulse/index.ts +59 -0
- package/.agents/skills/gstack/supabase/functions/telemetry-ingest/index.ts +135 -0
- package/.agents/skills/gstack/supabase/functions/update-check/index.ts +37 -0
- package/.agents/skills/gstack/supabase/migrations/001_telemetry.sql +89 -0
- package/.agents/skills/gstack/test/analytics.test.ts +277 -0
- package/.agents/skills/gstack/test/codex-e2e.test.ts +197 -0
- package/.agents/skills/gstack/test/fixtures/coverage-audit-fixture.ts +76 -0
- package/.agents/skills/gstack/test/fixtures/eval-baselines.json +7 -0
- package/.agents/skills/gstack/test/fixtures/qa-eval-checkout-ground-truth.json +43 -0
- package/.agents/skills/gstack/test/fixtures/qa-eval-ground-truth.json +43 -0
- package/.agents/skills/gstack/test/fixtures/qa-eval-spa-ground-truth.json +43 -0
- package/.agents/skills/gstack/test/fixtures/review-eval-design-slop.css +86 -0
- package/.agents/skills/gstack/test/fixtures/review-eval-design-slop.html +41 -0
- package/.agents/skills/gstack/test/fixtures/review-eval-enum-diff.rb +30 -0
- package/.agents/skills/gstack/test/fixtures/review-eval-enum.rb +27 -0
- package/.agents/skills/gstack/test/fixtures/review-eval-vuln.rb +14 -0
- package/.agents/skills/gstack/test/gemini-e2e.test.ts +173 -0
- package/.agents/skills/gstack/test/gen-skill-docs.test.ts +1049 -0
- package/.agents/skills/gstack/test/global-discover.test.ts +187 -0
- package/.agents/skills/gstack/test/helpers/codex-session-runner.ts +282 -0
- package/.agents/skills/gstack/test/helpers/e2e-helpers.ts +239 -0
- package/.agents/skills/gstack/test/helpers/eval-store.test.ts +548 -0
- package/.agents/skills/gstack/test/helpers/eval-store.ts +689 -0
- package/.agents/skills/gstack/test/helpers/gemini-session-runner.test.ts +104 -0
- package/.agents/skills/gstack/test/helpers/gemini-session-runner.ts +201 -0
- package/.agents/skills/gstack/test/helpers/llm-judge.ts +130 -0
- package/.agents/skills/gstack/test/helpers/observability.test.ts +283 -0
- package/.agents/skills/gstack/test/helpers/session-runner.test.ts +96 -0
- package/.agents/skills/gstack/test/helpers/session-runner.ts +357 -0
- package/.agents/skills/gstack/test/helpers/skill-parser.ts +206 -0
- package/.agents/skills/gstack/test/helpers/touchfiles.ts +260 -0
- package/.agents/skills/gstack/test/hook-scripts.test.ts +373 -0
- package/.agents/skills/gstack/test/skill-e2e-browse.test.ts +293 -0
- package/.agents/skills/gstack/test/skill-e2e-deploy.test.ts +279 -0
- package/.agents/skills/gstack/test/skill-e2e-design.test.ts +614 -0
- package/.agents/skills/gstack/test/skill-e2e-plan.test.ts +538 -0
- package/.agents/skills/gstack/test/skill-e2e-qa-bugs.test.ts +194 -0
- package/.agents/skills/gstack/test/skill-e2e-qa-workflow.test.ts +412 -0
- package/.agents/skills/gstack/test/skill-e2e-review.test.ts +535 -0
- package/.agents/skills/gstack/test/skill-e2e-workflow.test.ts +586 -0
- package/.agents/skills/gstack/test/skill-e2e.test.ts +3325 -0
- package/.agents/skills/gstack/test/skill-llm-eval.test.ts +787 -0
- package/.agents/skills/gstack/test/skill-parser.test.ts +179 -0
- package/.agents/skills/gstack/test/skill-routing-e2e.test.ts +605 -0
- package/.agents/skills/gstack/test/skill-validation.test.ts +1520 -0
- package/.agents/skills/gstack/test/telemetry.test.ts +278 -0
- package/.agents/skills/gstack/test/touchfiles.test.ts +262 -0
- package/.agents/skills/gstack/unfreeze/SKILL.md +40 -0
- package/.agents/skills/gstack/unfreeze/SKILL.md.tmpl +38 -0
- package/package.json +2 -1
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for cookie-picker route handler
|
|
3
|
+
*
|
|
4
|
+
* Tests the HTTP glue layer directly with mock BrowserManager objects.
|
|
5
|
+
* Verifies that all routes return valid JSON (not HTML) with correct CORS headers.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, test, expect } from 'bun:test';
|
|
9
|
+
import { handleCookiePickerRoute } from '../src/cookie-picker-routes';
|
|
10
|
+
|
|
11
|
+
// ─── Mock BrowserManager ──────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
function mockBrowserManager() {
|
|
14
|
+
const addedCookies: any[] = [];
|
|
15
|
+
const clearedDomains: string[] = [];
|
|
16
|
+
return {
|
|
17
|
+
bm: {
|
|
18
|
+
getPage: () => ({
|
|
19
|
+
context: () => ({
|
|
20
|
+
addCookies: (cookies: any[]) => { addedCookies.push(...cookies); },
|
|
21
|
+
clearCookies: (opts: { domain: string }) => { clearedDomains.push(opts.domain); },
|
|
22
|
+
}),
|
|
23
|
+
}),
|
|
24
|
+
} as any,
|
|
25
|
+
addedCookies,
|
|
26
|
+
clearedDomains,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function makeUrl(path: string, port = 9470): URL {
|
|
31
|
+
return new URL(`http://127.0.0.1:${port}${path}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function makeReq(method: string, body?: any): Request {
|
|
35
|
+
const opts: RequestInit = { method };
|
|
36
|
+
if (body) {
|
|
37
|
+
opts.body = JSON.stringify(body);
|
|
38
|
+
opts.headers = { 'Content-Type': 'application/json' };
|
|
39
|
+
}
|
|
40
|
+
return new Request('http://127.0.0.1:9470', opts);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ─── Tests ──────────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
describe('cookie-picker-routes', () => {
|
|
46
|
+
describe('CORS', () => {
|
|
47
|
+
test('OPTIONS returns 204 with correct CORS headers', async () => {
|
|
48
|
+
const { bm } = mockBrowserManager();
|
|
49
|
+
const url = makeUrl('/cookie-picker/browsers');
|
|
50
|
+
const req = new Request('http://127.0.0.1:9470', { method: 'OPTIONS' });
|
|
51
|
+
|
|
52
|
+
const res = await handleCookiePickerRoute(url, req, bm);
|
|
53
|
+
|
|
54
|
+
expect(res.status).toBe(204);
|
|
55
|
+
expect(res.headers.get('Access-Control-Allow-Origin')).toBe('http://127.0.0.1:9470');
|
|
56
|
+
expect(res.headers.get('Access-Control-Allow-Methods')).toContain('POST');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('JSON responses include correct CORS origin with port', async () => {
|
|
60
|
+
const { bm } = mockBrowserManager();
|
|
61
|
+
const url = makeUrl('/cookie-picker/browsers', 9450);
|
|
62
|
+
const req = new Request('http://127.0.0.1:9450', { method: 'GET' });
|
|
63
|
+
|
|
64
|
+
const res = await handleCookiePickerRoute(url, req, bm);
|
|
65
|
+
|
|
66
|
+
expect(res.headers.get('Access-Control-Allow-Origin')).toBe('http://127.0.0.1:9450');
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe('JSON responses (not HTML)', () => {
|
|
71
|
+
test('GET /cookie-picker/browsers returns JSON', async () => {
|
|
72
|
+
const { bm } = mockBrowserManager();
|
|
73
|
+
const url = makeUrl('/cookie-picker/browsers');
|
|
74
|
+
const req = new Request('http://127.0.0.1:9470', { method: 'GET' });
|
|
75
|
+
|
|
76
|
+
const res = await handleCookiePickerRoute(url, req, bm);
|
|
77
|
+
|
|
78
|
+
expect(res.status).toBe(200);
|
|
79
|
+
expect(res.headers.get('Content-Type')).toBe('application/json');
|
|
80
|
+
const body = await res.json();
|
|
81
|
+
expect(body).toHaveProperty('browsers');
|
|
82
|
+
expect(Array.isArray(body.browsers)).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('GET /cookie-picker/domains without browser param returns JSON error', async () => {
|
|
86
|
+
const { bm } = mockBrowserManager();
|
|
87
|
+
const url = makeUrl('/cookie-picker/domains');
|
|
88
|
+
const req = new Request('http://127.0.0.1:9470', { method: 'GET' });
|
|
89
|
+
|
|
90
|
+
const res = await handleCookiePickerRoute(url, req, bm);
|
|
91
|
+
|
|
92
|
+
expect(res.status).toBe(400);
|
|
93
|
+
expect(res.headers.get('Content-Type')).toBe('application/json');
|
|
94
|
+
const body = await res.json();
|
|
95
|
+
expect(body).toHaveProperty('error');
|
|
96
|
+
expect(body).toHaveProperty('code', 'missing_param');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('POST /cookie-picker/import with invalid JSON returns JSON error', async () => {
|
|
100
|
+
const { bm } = mockBrowserManager();
|
|
101
|
+
const url = makeUrl('/cookie-picker/import');
|
|
102
|
+
const req = new Request('http://127.0.0.1:9470', {
|
|
103
|
+
method: 'POST',
|
|
104
|
+
body: 'not json',
|
|
105
|
+
headers: { 'Content-Type': 'application/json' },
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const res = await handleCookiePickerRoute(url, req, bm);
|
|
109
|
+
|
|
110
|
+
expect(res.status).toBe(400);
|
|
111
|
+
expect(res.headers.get('Content-Type')).toBe('application/json');
|
|
112
|
+
const body = await res.json();
|
|
113
|
+
expect(body.code).toBe('bad_request');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('POST /cookie-picker/import missing browser field returns JSON error', async () => {
|
|
117
|
+
const { bm } = mockBrowserManager();
|
|
118
|
+
const url = makeUrl('/cookie-picker/import');
|
|
119
|
+
const req = makeReq('POST', { domains: ['.example.com'] });
|
|
120
|
+
|
|
121
|
+
const res = await handleCookiePickerRoute(url, req, bm);
|
|
122
|
+
|
|
123
|
+
expect(res.status).toBe(400);
|
|
124
|
+
const body = await res.json();
|
|
125
|
+
expect(body.code).toBe('missing_param');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('POST /cookie-picker/import missing domains returns JSON error', async () => {
|
|
129
|
+
const { bm } = mockBrowserManager();
|
|
130
|
+
const url = makeUrl('/cookie-picker/import');
|
|
131
|
+
const req = makeReq('POST', { browser: 'Chrome' });
|
|
132
|
+
|
|
133
|
+
const res = await handleCookiePickerRoute(url, req, bm);
|
|
134
|
+
|
|
135
|
+
expect(res.status).toBe(400);
|
|
136
|
+
const body = await res.json();
|
|
137
|
+
expect(body.code).toBe('missing_param');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test('POST /cookie-picker/remove with invalid JSON returns JSON error', async () => {
|
|
141
|
+
const { bm } = mockBrowserManager();
|
|
142
|
+
const url = makeUrl('/cookie-picker/remove');
|
|
143
|
+
const req = new Request('http://127.0.0.1:9470', {
|
|
144
|
+
method: 'POST',
|
|
145
|
+
body: '{bad',
|
|
146
|
+
headers: { 'Content-Type': 'application/json' },
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const res = await handleCookiePickerRoute(url, req, bm);
|
|
150
|
+
|
|
151
|
+
expect(res.status).toBe(400);
|
|
152
|
+
expect(res.headers.get('Content-Type')).toBe('application/json');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test('POST /cookie-picker/remove missing domains returns JSON error', async () => {
|
|
156
|
+
const { bm } = mockBrowserManager();
|
|
157
|
+
const url = makeUrl('/cookie-picker/remove');
|
|
158
|
+
const req = makeReq('POST', {});
|
|
159
|
+
|
|
160
|
+
const res = await handleCookiePickerRoute(url, req, bm);
|
|
161
|
+
|
|
162
|
+
expect(res.status).toBe(400);
|
|
163
|
+
const body = await res.json();
|
|
164
|
+
expect(body.code).toBe('missing_param');
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test('GET /cookie-picker/imported returns JSON with domain list', async () => {
|
|
168
|
+
const { bm } = mockBrowserManager();
|
|
169
|
+
const url = makeUrl('/cookie-picker/imported');
|
|
170
|
+
const req = new Request('http://127.0.0.1:9470', { method: 'GET' });
|
|
171
|
+
|
|
172
|
+
const res = await handleCookiePickerRoute(url, req, bm);
|
|
173
|
+
|
|
174
|
+
expect(res.status).toBe(200);
|
|
175
|
+
expect(res.headers.get('Content-Type')).toBe('application/json');
|
|
176
|
+
const body = await res.json();
|
|
177
|
+
expect(body).toHaveProperty('domains');
|
|
178
|
+
expect(body).toHaveProperty('totalDomains');
|
|
179
|
+
expect(body).toHaveProperty('totalCookies');
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe('routing', () => {
|
|
184
|
+
test('GET /cookie-picker returns HTML', async () => {
|
|
185
|
+
const { bm } = mockBrowserManager();
|
|
186
|
+
const url = makeUrl('/cookie-picker');
|
|
187
|
+
const req = new Request('http://127.0.0.1:9470', { method: 'GET' });
|
|
188
|
+
|
|
189
|
+
const res = await handleCookiePickerRoute(url, req, bm);
|
|
190
|
+
|
|
191
|
+
expect(res.status).toBe(200);
|
|
192
|
+
expect(res.headers.get('Content-Type')).toContain('text/html');
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test('unknown path returns 404', async () => {
|
|
196
|
+
const { bm } = mockBrowserManager();
|
|
197
|
+
const url = makeUrl('/cookie-picker/nonexistent');
|
|
198
|
+
const req = new Request('http://127.0.0.1:9470', { method: 'GET' });
|
|
199
|
+
|
|
200
|
+
const res = await handleCookiePickerRoute(url, req, bm);
|
|
201
|
+
|
|
202
|
+
expect(res.status).toBe(404);
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for find-browse binary locator.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, test, expect } from 'bun:test';
|
|
6
|
+
import { locateBinary } from '../src/find-browse';
|
|
7
|
+
import { existsSync } from 'fs';
|
|
8
|
+
|
|
9
|
+
describe('locateBinary', () => {
|
|
10
|
+
test('returns null when no binary exists at known paths', () => {
|
|
11
|
+
// This test depends on the test environment — if a real binary exists at
|
|
12
|
+
// ~/.claude/skills/gstack/browse/dist/browse, it will find it.
|
|
13
|
+
// We mainly test that the function doesn't throw.
|
|
14
|
+
const result = locateBinary();
|
|
15
|
+
expect(result === null || typeof result === 'string').toBe(true);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('returns string path when binary exists', () => {
|
|
19
|
+
const result = locateBinary();
|
|
20
|
+
if (result !== null) {
|
|
21
|
+
expect(existsSync(result)).toBe(true);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('priority chain checks .codex, .agents, .claude markers', () => {
|
|
26
|
+
// Verify the source code implements the correct priority order.
|
|
27
|
+
// We read the function source to confirm the markers array order.
|
|
28
|
+
const src = require('fs').readFileSync(require('path').join(__dirname, '../src/find-browse.ts'), 'utf-8');
|
|
29
|
+
// The markers array should list .codex first, then .agents, then .claude
|
|
30
|
+
const markersMatch = src.match(/const markers = \[([^\]]+)\]/);
|
|
31
|
+
expect(markersMatch).not.toBeNull();
|
|
32
|
+
const markers = markersMatch![1];
|
|
33
|
+
const codexIdx = markers.indexOf('.codex');
|
|
34
|
+
const agentsIdx = markers.indexOf('.agents');
|
|
35
|
+
const claudeIdx = markers.indexOf('.claude');
|
|
36
|
+
// All three must be present
|
|
37
|
+
expect(codexIdx).toBeGreaterThanOrEqual(0);
|
|
38
|
+
expect(agentsIdx).toBeGreaterThanOrEqual(0);
|
|
39
|
+
expect(claudeIdx).toBeGreaterThanOrEqual(0);
|
|
40
|
+
// .codex before .agents before .claude
|
|
41
|
+
expect(codexIdx).toBeLessThan(agentsIdx);
|
|
42
|
+
expect(agentsIdx).toBeLessThan(claudeIdx);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('function signature accepts no arguments', () => {
|
|
46
|
+
// locateBinary should be callable with no arguments
|
|
47
|
+
expect(typeof locateBinary).toBe('function');
|
|
48
|
+
expect(locateBinary.length).toBe(0);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>Test Page - Basic</title>
|
|
6
|
+
<style>
|
|
7
|
+
body { font-family: "Helvetica Neue", sans-serif; color: #333; margin: 20px; }
|
|
8
|
+
h1 { color: navy; font-size: 24px; }
|
|
9
|
+
.highlight { background: yellow; padding: 4px; }
|
|
10
|
+
.hidden { display: none; }
|
|
11
|
+
nav a { margin-right: 10px; color: blue; }
|
|
12
|
+
</style>
|
|
13
|
+
</head>
|
|
14
|
+
<body>
|
|
15
|
+
<nav>
|
|
16
|
+
<a href="/page1">Page 1</a>
|
|
17
|
+
<a href="/page2">Page 2</a>
|
|
18
|
+
<a href="https://external.com/link">External</a>
|
|
19
|
+
</nav>
|
|
20
|
+
<h1 id="title">Hello World</h1>
|
|
21
|
+
<p class="highlight">This is a highlighted paragraph.</p>
|
|
22
|
+
<p class="hidden">This should be hidden.</p>
|
|
23
|
+
<div id="content" data-testid="main-content" data-version="1.0">
|
|
24
|
+
<p>Some body text here.</p>
|
|
25
|
+
<ul>
|
|
26
|
+
<li>Item one</li>
|
|
27
|
+
<li>Item two</li>
|
|
28
|
+
<li>Item three</li>
|
|
29
|
+
</ul>
|
|
30
|
+
</div>
|
|
31
|
+
<footer>Footer text</footer>
|
|
32
|
+
</body>
|
|
33
|
+
</html>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>Test Page - Cursor Interactive</title>
|
|
6
|
+
<style>
|
|
7
|
+
.clickable-div { cursor: pointer; padding: 10px; border: 1px solid #ccc; }
|
|
8
|
+
.hover-card { cursor: pointer; padding: 20px; background: #f0f0f0; }
|
|
9
|
+
</style>
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
<h1>Cursor Interactive Test</h1>
|
|
13
|
+
<!-- These are NOT standard interactive elements but have cursor:pointer -->
|
|
14
|
+
<div class="clickable-div" id="click-div" onclick="this.textContent = 'clicked!'">Click me (div)</div>
|
|
15
|
+
<span class="hover-card" id="hover-span">Hover card (span)</span>
|
|
16
|
+
<div tabindex="0" id="focusable-div">Focusable div</div>
|
|
17
|
+
<div onclick="alert('hi')" id="onclick-div">Onclick div</div>
|
|
18
|
+
<!-- Standard interactive element (should NOT appear in -C output) -->
|
|
19
|
+
<button id="normal-btn">Normal Button</button>
|
|
20
|
+
<a href="/test">Normal Link</a>
|
|
21
|
+
</body>
|
|
22
|
+
</html>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>Test Page - Dialog</title>
|
|
6
|
+
</head>
|
|
7
|
+
<body>
|
|
8
|
+
<h1>Dialog Test</h1>
|
|
9
|
+
<button id="alert-btn" onclick="alert('Hello from alert')">Alert</button>
|
|
10
|
+
<button id="confirm-btn" onclick="document.getElementById('confirm-result').textContent = confirm('Are you sure?') ? 'confirmed' : 'cancelled'">Confirm</button>
|
|
11
|
+
<button id="prompt-btn" onclick="document.getElementById('prompt-result').textContent = prompt('Enter name:', 'default') || 'null'">Prompt</button>
|
|
12
|
+
<p id="confirm-result"></p>
|
|
13
|
+
<p id="prompt-result"></p>
|
|
14
|
+
</body>
|
|
15
|
+
</html>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>Test Page - Forms</title>
|
|
6
|
+
<style>
|
|
7
|
+
body { font-family: sans-serif; padding: 20px; }
|
|
8
|
+
form { margin-bottom: 20px; padding: 10px; border: 1px solid #ccc; }
|
|
9
|
+
label { display: block; margin: 5px 0; }
|
|
10
|
+
input, select, textarea { margin-bottom: 10px; padding: 5px; }
|
|
11
|
+
#result { color: green; display: none; }
|
|
12
|
+
</style>
|
|
13
|
+
</head>
|
|
14
|
+
<body>
|
|
15
|
+
<h1>Form Test Page</h1>
|
|
16
|
+
|
|
17
|
+
<form id="login-form" action="/login" method="post">
|
|
18
|
+
<label for="email">Email:</label>
|
|
19
|
+
<input type="email" id="email" name="email" placeholder="your@email.com" required>
|
|
20
|
+
<label for="password">Password:</label>
|
|
21
|
+
<input type="password" id="password" name="password" required>
|
|
22
|
+
<button type="submit" id="login-btn">Log In</button>
|
|
23
|
+
</form>
|
|
24
|
+
|
|
25
|
+
<form id="profile-form" action="/profile" method="post">
|
|
26
|
+
<label for="name">Name:</label>
|
|
27
|
+
<input type="text" id="name" name="name" placeholder="Your name">
|
|
28
|
+
<label for="bio">Bio:</label>
|
|
29
|
+
<textarea id="bio" name="bio" placeholder="Tell us about yourself"></textarea>
|
|
30
|
+
<label for="role">Role:</label>
|
|
31
|
+
<select id="role" name="role">
|
|
32
|
+
<option value="">Choose...</option>
|
|
33
|
+
<option value="admin">Admin</option>
|
|
34
|
+
<option value="user">User</option>
|
|
35
|
+
<option value="guest">Guest</option>
|
|
36
|
+
</select>
|
|
37
|
+
<label>
|
|
38
|
+
<input type="checkbox" id="newsletter" name="newsletter"> Subscribe to newsletter
|
|
39
|
+
</label>
|
|
40
|
+
<button type="submit" id="profile-btn">Save Profile</button>
|
|
41
|
+
</form>
|
|
42
|
+
|
|
43
|
+
<div id="result">Form submitted!</div>
|
|
44
|
+
|
|
45
|
+
<script>
|
|
46
|
+
document.querySelectorAll('form').forEach(form => {
|
|
47
|
+
form.addEventListener('submit', (e) => {
|
|
48
|
+
e.preventDefault();
|
|
49
|
+
document.getElementById('result').style.display = 'block';
|
|
50
|
+
console.log('[Form] Submitted:', form.id);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
</script>
|
|
54
|
+
</body>
|
|
55
|
+
</html>
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>QA Eval — Checkout</title>
|
|
6
|
+
<style>
|
|
7
|
+
body { font-family: sans-serif; padding: 20px; }
|
|
8
|
+
.checkout-form { max-width: 500px; }
|
|
9
|
+
.form-group { margin-bottom: 15px; }
|
|
10
|
+
.form-group label { display: block; margin-bottom: 4px; font-weight: bold; }
|
|
11
|
+
.form-group input { width: 100%; padding: 8px; box-sizing: border-box; border: 1px solid #ccc; border-radius: 4px; }
|
|
12
|
+
.form-group input.invalid { border-color: red; }
|
|
13
|
+
.form-group .error-msg { color: red; font-size: 12px; display: none; }
|
|
14
|
+
.total { font-size: 24px; font-weight: bold; margin: 20px 0; }
|
|
15
|
+
button[type="submit"] { padding: 12px 24px; background: #0066cc; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; }
|
|
16
|
+
.order-summary { background: #f5f5f5; padding: 15px; border-radius: 4px; margin-bottom: 20px; }
|
|
17
|
+
</style>
|
|
18
|
+
</head>
|
|
19
|
+
<body>
|
|
20
|
+
<h1>Checkout</h1>
|
|
21
|
+
|
|
22
|
+
<div class="order-summary">
|
|
23
|
+
<h2>Order Summary</h2>
|
|
24
|
+
<p>Widget Pro — $99.99 x <input type="number" id="quantity" value="1" min="1" style="width: 50px;"></p>
|
|
25
|
+
<p class="total" id="total">Total: $99.99</p> <!-- BUG 2: shows $NaN when quantity is cleared -->
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<form class="checkout-form" id="checkout-form">
|
|
29
|
+
<h2>Shipping Information</h2>
|
|
30
|
+
|
|
31
|
+
<div class="form-group">
|
|
32
|
+
<label for="email">Email</label>
|
|
33
|
+
<input type="text" id="email" name="email" placeholder="you@example.com" required
|
|
34
|
+
pattern="[^@]+@[^@]"> <!-- BUG 1: broken regex — accepts "user@" as valid -->
|
|
35
|
+
<span class="error-msg" id="email-error">Please enter a valid email</span>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<div class="form-group">
|
|
39
|
+
<label for="address">Address</label>
|
|
40
|
+
<input type="text" id="address" name="address" placeholder="123 Main St" required>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<div class="form-group">
|
|
44
|
+
<label for="city">City</label>
|
|
45
|
+
<input type="text" id="city" name="city" placeholder="San Francisco" required>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<div class="form-group">
|
|
49
|
+
<label for="zip">Zip Code</label>
|
|
50
|
+
<input type="text" id="zip" name="zip" placeholder="94105"> <!-- BUG 4: missing required attribute -->
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<h2>Payment</h2>
|
|
54
|
+
|
|
55
|
+
<div class="form-group">
|
|
56
|
+
<label for="cc">Credit Card Number</label>
|
|
57
|
+
<input type="text" id="cc" name="cc" placeholder="4111 1111 1111 1111" required>
|
|
58
|
+
<!-- BUG 3: no maxlength — overflows container at >20 chars -->
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<div class="form-group">
|
|
62
|
+
<label for="exp">Expiration</label>
|
|
63
|
+
<input type="text" id="exp" name="exp" placeholder="MM/YY" required maxlength="5">
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<div class="form-group">
|
|
67
|
+
<label for="cvv">CVV</label>
|
|
68
|
+
<input type="text" id="cvv" name="cvv" placeholder="123" required maxlength="4">
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<button type="submit">Place Order — $<span id="submit-total">99.99</span></button>
|
|
72
|
+
</form>
|
|
73
|
+
|
|
74
|
+
<script>
|
|
75
|
+
// Update total when quantity changes
|
|
76
|
+
const quantityInput = document.getElementById('quantity');
|
|
77
|
+
const totalEl = document.getElementById('total');
|
|
78
|
+
const submitTotalEl = document.getElementById('submit-total');
|
|
79
|
+
|
|
80
|
+
quantityInput.addEventListener('input', () => {
|
|
81
|
+
// BUG 2: parseInt on empty string returns NaN, no fallback
|
|
82
|
+
const qty = parseInt(quantityInput.value);
|
|
83
|
+
const total = (qty * 99.99).toFixed(2);
|
|
84
|
+
totalEl.textContent = 'Total: $' + total;
|
|
85
|
+
submitTotalEl.textContent = total;
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Email validation (broken)
|
|
89
|
+
const emailInput = document.getElementById('email');
|
|
90
|
+
emailInput.addEventListener('blur', () => {
|
|
91
|
+
// BUG 1: this regex accepts "user@" — missing domain part check
|
|
92
|
+
const valid = /[^@]+@/.test(emailInput.value);
|
|
93
|
+
emailInput.classList.toggle('invalid', !valid && emailInput.value.length > 0);
|
|
94
|
+
document.getElementById('email-error').style.display = (!valid && emailInput.value.length > 0) ? 'block' : 'none';
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Form submit
|
|
98
|
+
document.getElementById('checkout-form').addEventListener('submit', (e) => {
|
|
99
|
+
e.preventDefault();
|
|
100
|
+
// BUG 5: stripe is not defined — console error on submit
|
|
101
|
+
stripe.createPaymentMethod({
|
|
102
|
+
type: 'card',
|
|
103
|
+
card: { number: document.getElementById('cc').value }
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
</script>
|
|
107
|
+
</body>
|
|
108
|
+
</html>
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>QA Eval — SPA Store</title>
|
|
6
|
+
<style>
|
|
7
|
+
body { font-family: sans-serif; padding: 20px; margin: 0; }
|
|
8
|
+
nav { background: #333; padding: 10px 20px; }
|
|
9
|
+
nav a { color: white; margin-right: 15px; text-decoration: none; cursor: pointer; }
|
|
10
|
+
nav a:hover { text-decoration: underline; }
|
|
11
|
+
#app { padding: 20px; }
|
|
12
|
+
.product { border: 1px solid #ddd; padding: 10px; margin: 10px 0; border-radius: 4px; }
|
|
13
|
+
.product button { padding: 6px 12px; background: #0066cc; color: white; border: none; cursor: pointer; }
|
|
14
|
+
.cart-count { background: #cc0000; color: white; padding: 2px 8px; border-radius: 10px; font-size: 12px; }
|
|
15
|
+
.error { color: red; padding: 10px; }
|
|
16
|
+
.loading { color: #666; padding: 10px; }
|
|
17
|
+
</style>
|
|
18
|
+
</head>
|
|
19
|
+
<body>
|
|
20
|
+
<nav>
|
|
21
|
+
<a href="#/home">Home</a>
|
|
22
|
+
<a href="#/prodcts">Products</a> <!-- BUG 1: broken route — typo "prodcts" instead of "products" -->
|
|
23
|
+
<a href="#/contact">Contact</a>
|
|
24
|
+
<span class="cart-count" id="cart-count">0</span>
|
|
25
|
+
</nav>
|
|
26
|
+
|
|
27
|
+
<div id="app">
|
|
28
|
+
<p>Welcome to SPA Store. Use the navigation above.</p>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<script>
|
|
32
|
+
let cartCount = 0;
|
|
33
|
+
|
|
34
|
+
// BUG 2: cart count never resets on route change — stale state
|
|
35
|
+
function addToCart() {
|
|
36
|
+
cartCount++;
|
|
37
|
+
document.getElementById('cart-count').textContent = cartCount;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function renderHome() {
|
|
41
|
+
document.getElementById('app').innerHTML = `
|
|
42
|
+
<h1>Welcome to SPA Store</h1>
|
|
43
|
+
<p>Browse our products using the navigation above.</p>
|
|
44
|
+
`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function renderProducts() {
|
|
48
|
+
document.getElementById('app').innerHTML = '<p class="loading">Loading products...</p>';
|
|
49
|
+
|
|
50
|
+
// BUG 3: async race — shows data briefly, then shows error
|
|
51
|
+
setTimeout(() => {
|
|
52
|
+
document.getElementById('app').innerHTML = `
|
|
53
|
+
<h1>Products</h1>
|
|
54
|
+
<div class="product">
|
|
55
|
+
<h3>Widget A</h3>
|
|
56
|
+
<p>$29.99</p>
|
|
57
|
+
<button onclick="addToCart()">Add to Cart</button>
|
|
58
|
+
</div>
|
|
59
|
+
<div class="product">
|
|
60
|
+
<h3>Widget B</h3>
|
|
61
|
+
<p>$49.99</p>
|
|
62
|
+
<button onclick="addToCart()">Add to Cart</button>
|
|
63
|
+
</div>
|
|
64
|
+
`;
|
|
65
|
+
}, 300);
|
|
66
|
+
|
|
67
|
+
setTimeout(() => {
|
|
68
|
+
document.getElementById('app').innerHTML = '<p class="error">Error: Failed to fetch products from API</p>';
|
|
69
|
+
}, 1000);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function renderContact() {
|
|
73
|
+
document.getElementById('app').innerHTML = `
|
|
74
|
+
<h1>Contact Us</h1>
|
|
75
|
+
<p>Email: support@spastore.example.com</p>
|
|
76
|
+
`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// BUG 4: nav links have no aria-current attribute on active route
|
|
80
|
+
function router() {
|
|
81
|
+
const hash = window.location.hash || '#/home';
|
|
82
|
+
switch (hash) {
|
|
83
|
+
case '#/home': renderHome(); break;
|
|
84
|
+
case '#/products': renderProducts(); break;
|
|
85
|
+
case '#/contact': renderContact(); break;
|
|
86
|
+
default:
|
|
87
|
+
document.getElementById('app').innerHTML = '<p>Page not found</p>';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// BUG 5: console.warn on every route change — simulates listener leak
|
|
91
|
+
console.warn('Possible memory leak detected: 11 event listeners added to window');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
window.addEventListener('hashchange', router);
|
|
95
|
+
router();
|
|
96
|
+
</script>
|
|
97
|
+
</body>
|
|
98
|
+
</html>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>QA Eval — Widget Dashboard</title>
|
|
6
|
+
<style>
|
|
7
|
+
body { font-family: sans-serif; padding: 20px; }
|
|
8
|
+
nav { margin-bottom: 20px; }
|
|
9
|
+
nav a { margin-right: 15px; color: #0066cc; }
|
|
10
|
+
form { margin: 20px 0; padding: 15px; border: 1px solid #ccc; border-radius: 4px; }
|
|
11
|
+
input { display: block; margin: 8px 0; padding: 6px; }
|
|
12
|
+
button { padding: 8px 16px; margin-top: 8px; }
|
|
13
|
+
.stats { margin: 20px 0; }
|
|
14
|
+
img { display: block; margin: 20px 0; }
|
|
15
|
+
</style>
|
|
16
|
+
</head>
|
|
17
|
+
<body>
|
|
18
|
+
<nav>
|
|
19
|
+
<a href="/">Home</a>
|
|
20
|
+
<a href="/about">About</a>
|
|
21
|
+
<a href="/nonexistent-404-page">Resources</a> <!-- BUG 1: broken link (404) -->
|
|
22
|
+
</nav>
|
|
23
|
+
|
|
24
|
+
<h1>Widget Dashboard</h1>
|
|
25
|
+
|
|
26
|
+
<form id="contact">
|
|
27
|
+
<h2>Contact Us</h2>
|
|
28
|
+
<input type="text" name="name" placeholder="Name" required>
|
|
29
|
+
<input type="email" name="email" placeholder="Email" required>
|
|
30
|
+
<button type="submit" disabled>Submit</button> <!-- BUG 2: submit button permanently disabled -->
|
|
31
|
+
</form>
|
|
32
|
+
|
|
33
|
+
<div class="stats" style="width: 400px; overflow: hidden;">
|
|
34
|
+
<h2>Statistics</h2>
|
|
35
|
+
<p style="white-space: nowrap; width: 600px;">
|
|
36
|
+
Revenue: $1,234,567.89 | Users: 45,678 | Conversion: 3.2% | Growth: +12.5% MoM | Retention: 87.3%
|
|
37
|
+
</p> <!-- BUG 3: content overflow/clipping — text wider than container with overflow:hidden -->
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<img src="/logo.png"> <!-- BUG 4: missing alt text on image -->
|
|
41
|
+
|
|
42
|
+
<footer>
|
|
43
|
+
<p>© 2026 Widget Co. All rights reserved.</p>
|
|
44
|
+
</footer>
|
|
45
|
+
|
|
46
|
+
<script>
|
|
47
|
+
console.error("TypeError: Cannot read properties of undefined (reading 'map')");
|
|
48
|
+
// BUG 5: console error on page load
|
|
49
|
+
</script>
|
|
50
|
+
</body>
|
|
51
|
+
</html>
|