/** * lib/frontend-scope.ts mirrors the m_frontend arm of bin/gstack-diff-scope. * Pure cases run everywhere; the parity case runs the bash script in a temp * repo (POSIX only) so the two implementations cannot drift silently. */ import { describe, test, expect } from 'bun:test'; import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; import { spawnSync } from 'child_process'; import { TEMPLATE_TAG, TEMPLATE_TAG_ERE, TEST_PATH_ERE, isFrontendPath, isTemplateCandidate } from '../lib/frontend-scope'; const ROOT = path.join(import.meta.dir, '..'); const POSIX = process.platform !== 'win32'; const SAMPLES: Array<[string, boolean]> = [ ['src/components/Button.tsx', true], ['src/Button.jsx', true], ['pages/index.vue', true], ['app/Widget.svelte', true], ['site/page.astro', true], ['styles/main.css', true], ['css/a.scss', true], ['x/y/theme.less', true], ['x/a.sass', true], ['x/a.pcss', true], ['app/views/users/show.html.erb', true], ['templates/a.haml', true], ['templates/a.slim', true], ['templates/a.hbs', true], ['views/a.ejs', true], ['public/index.html', true], ['tailwind.config.js', true], ['postcss.config.cjs', true], ['src/tailwind.config.js', false], // the bash glob is matched against the whole repo-relative path: root-level configs only ['packages/ui/postcss.config.cjs', false], ['app/assets/stylesheets/app.css', true], ['lib/util/components/helper.rb', true], ['lib/server.ts', false], ['src/api/route.js', false], ['README.md', false], ['package.json', false], ['test/foo.test.ts', false], ['components.md', false], ['public/Index.HTML', false], // bash globs are case-sensitive; the mirror must agree ['src/App.TSX', false], ['lib/widgets/bbtm_drawer/view/bbtm_drawer.dart', true], ['packages/cache/lib/cache.dart', true], ['test/ui/drawer_widget_test.dart', true], ]; describe('isFrontendPath', () => { test.each(SAMPLES)('%s → %p', (p, expected) => { expect(isFrontendPath(p)).toBe(expected); }); test('normalizes leading ./ and backslashes', () => { expect(isFrontendPath('./styles/a.css')).toBe(true); expect(isFrontendPath('src\\components\\A.tsx')).toBe(true); }); }); describe('template content arm (GST-1718 regex, reused by /autoplan in GST-1750)', () => { test('TEMPLATE_TAG_ERE and TEST_PATH_ERE are byte-identical to the script', () => { const script = fs.readFileSync(path.join(ROOT, 'bin', 'gstack-diff-scope'), 'utf-8'); expect(script.match(/^TEMPLATE_TAG_RE='([^']+)'$/m)?.[1]).toBe(TEMPLATE_TAG_ERE); expect(script.match(/^TEST_PATH_RE='([^']+)'$/m)?.[1]).toBe(TEST_PATH_ERE); }); test('TEMPLATE_TAG matches a tag before a backtick on one line, as grep -E does', () => { for (const s of ['return html`
`', 'x = svg `