mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 23:45:18 +00:00
A fixture B196 'chassis elsewhere' criava 5 atividades com startAt fixo em 2026-09-01. A aba default de /campanha/atividades (proximos) filtra status=confirmado e startAt >= now, então a partir de 02/09 a lista virou empty state, o scrollport perdeu overflow e a assert scrollHeight - clientHeight > 0 falhou nas 3 retries do verify do deploy de 2026-09-11 (run 34652323456), bloqueando o job de deploy. Deriva o startAt de hoje (+7 dias, tempo civil da Bahia) como os specs de agenda já fazem, dá ao poll do scrollport o orçamento de 30s da classe de latência B196 documentada no arquivo, e adiciona guard ESLint (no-restricted-syntax escopado a tests/e2e) que bane ISO literal em propriedades start*/end*/date* de fixture — pinado em eslintConventions.unit.spec.ts.
218 lines
7.7 KiB
TypeScript
218 lines
7.7 KiB
TypeScript
import { ESLint } from 'eslint'
|
|
import { describe, expect, it } from 'vitest'
|
|
|
|
const eslint = new ESLint({
|
|
cwd: process.cwd(),
|
|
cache: false,
|
|
})
|
|
|
|
const fixingEslint = new ESLint({
|
|
cwd: process.cwd(),
|
|
cache: false,
|
|
fix: true,
|
|
})
|
|
|
|
const lint = async (filePath: string, code: string) => {
|
|
const [result] = await eslint.lintText(code, { filePath })
|
|
|
|
return {
|
|
errors: result.messages.filter(({ severity }) => severity === 2),
|
|
output: result.output,
|
|
}
|
|
}
|
|
|
|
const lintWithFix = async (filePath: string, code: string) => {
|
|
const [result] = await fixingEslint.lintText(code, { filePath })
|
|
|
|
return result.output
|
|
}
|
|
|
|
const expectValid = async (filePath: string, code: string) => {
|
|
const { errors } = await lint(filePath, code)
|
|
|
|
expect(errors).toEqual([])
|
|
}
|
|
|
|
const expectInvalid = async (filePath: string, code: string, ruleId: string) => {
|
|
const { errors } = await lint(filePath, code)
|
|
|
|
expect(errors.some((error) => error.ruleId === ruleId)).toBe(true)
|
|
}
|
|
|
|
describe('ESLint conventions', () => {
|
|
it('enforces PascalCase component and camelCase utility/test filenames', async () => {
|
|
await expectValid('src/components/FutureCard.tsx', 'export const FutureCard = () => <div />')
|
|
await expectInvalid(
|
|
'src/components/future-card.tsx',
|
|
'export const FutureCard = () => <div />',
|
|
'check-file/filename-naming-convention',
|
|
)
|
|
await expectValid('src/utilities/futureValue.ts', 'export const futureValue = 1')
|
|
await expectValid('tests/int/futureValue.int.spec.ts', 'export const futureValue = 1')
|
|
await expectInvalid(
|
|
'tests/int/future-value.int.spec.ts',
|
|
'export const futureValue = 1',
|
|
'check-file/filename-naming-convention',
|
|
)
|
|
}, 15_000)
|
|
|
|
it('requires uppercase component arrows to use const', async () => {
|
|
await expectInvalid(
|
|
'src/components/FutureCard.tsx',
|
|
'export let FutureCard = () => <div />',
|
|
'local/component-arrow-conventions',
|
|
)
|
|
})
|
|
|
|
it('requires implicit JSX returns only for pure component arrows', async () => {
|
|
const code = 'export const FutureCard = () => { return <div /> }'
|
|
const result = await lint('src/components/FutureCard.tsx', code)
|
|
const output = await lintWithFix('src/components/FutureCard.tsx', code)
|
|
|
|
expect(
|
|
result.errors.some((error) => error.ruleId === 'local/component-arrow-conventions'),
|
|
).toBe(true)
|
|
expect(output).toContain('export const FutureCard = () => (<div />)')
|
|
|
|
await expectValid(
|
|
'src/components/FutureList.tsx',
|
|
'export const FutureList = () => { const items = [1]; return items.map((item) => { return <div key={item}>{item}</div> }) }',
|
|
)
|
|
})
|
|
|
|
it('reports pure JSX component returns without deleting block comments', async () => {
|
|
const code = 'export const FutureCard = () => { /* keep this rationale */ return <div /> }'
|
|
const result = await lint('src/components/FutureCard.tsx', code)
|
|
const output = (await lintWithFix('src/components/FutureCard.tsx', code)) ?? code
|
|
|
|
expect(
|
|
result.errors.some((error) => error.ruleId === 'local/component-arrow-conventions'),
|
|
).toBe(true)
|
|
expect(output).toContain('keep this rationale')
|
|
})
|
|
|
|
it('allows callback block returns without applying arrow-body-style globally', async () => {
|
|
await expectValid(
|
|
'src/components/FutureList.tsx',
|
|
'export const FutureList = () => [1].map((item) => { return <div key={item}>{item}</div> })',
|
|
)
|
|
})
|
|
|
|
it('rejects every ordinary component default export form', async () => {
|
|
const examples = [
|
|
'export default function FutureCard() { return <div /> }',
|
|
'const futureCard = () => <div />; export default futureCard',
|
|
'export default memo(() => <div />)',
|
|
'export default (function FutureCard() { return <div /> })',
|
|
'export default function () { return <div /> }',
|
|
'export default (() => <div />)',
|
|
]
|
|
|
|
for (const code of examples) {
|
|
await expectInvalid(
|
|
'src/components/FutureCard.tsx',
|
|
code,
|
|
'local/no-component-default-export',
|
|
)
|
|
}
|
|
})
|
|
|
|
it('accepts only an inline named function default in Next framework files', async () => {
|
|
await expectValid(
|
|
'src/app/future/page.tsx',
|
|
'export default function FuturePage() { return <div /> }',
|
|
)
|
|
|
|
const invalidExamples = [
|
|
'const futurePage = () => <div />; export default futurePage',
|
|
'const FuturePage = () => <div />; export default FuturePage',
|
|
'export default memo(() => <div />)',
|
|
'export default (function FuturePage() { return <div /> })',
|
|
'export default (() => <div />)',
|
|
'export default function () { return <div /> }',
|
|
]
|
|
|
|
for (const code of invalidExamples) {
|
|
await expectInvalid('src/app/future/page.tsx', code, 'local/framework-default-export')
|
|
}
|
|
})
|
|
|
|
it('requires Next framework files to have a default export', async () => {
|
|
await expectInvalid(
|
|
'src/app/future/page.tsx',
|
|
'export function FuturePage() { return <div /> }',
|
|
'local/framework-default-export',
|
|
)
|
|
})
|
|
|
|
it('limits Payload admin exemptions to the exact existing catch-all files', async () => {
|
|
await expectValid(
|
|
'src/app/(payload)/admin/[[...segments]]/page.tsx',
|
|
'const Page = () => <div />; export default Page',
|
|
)
|
|
await expectValid(
|
|
'src/app/(payload)/admin/[[...segments]]/not-found.tsx',
|
|
'const NotFound = () => <div />; export default NotFound',
|
|
)
|
|
await expectInvalid(
|
|
'src/app/(payload)/admin/future/page.tsx',
|
|
'export function FutureAdminPage() { return <div /> }',
|
|
'local/framework-default-export',
|
|
)
|
|
})
|
|
|
|
it('exempts non-component config object defaults', async () => {
|
|
await expectValid(
|
|
'src/utilities/futureConfig.ts',
|
|
'const futureConfig = { enabled: true }; export default futureConfig',
|
|
)
|
|
})
|
|
|
|
it('rejects non-async exports from top-level use server modules', async () => {
|
|
await expectInvalid(
|
|
'src/app/(campaign)/campanha/actions/futureAction.ts',
|
|
`'use server'\n\nexport const safeMessages = ['x'] as const\n`,
|
|
'local/use-server-async-exports',
|
|
)
|
|
await expectInvalid(
|
|
'src/app/(campaign)/campanha/actions/futureAction.ts',
|
|
`'use server'\n\nexport function syncAction() { return 1 }\n`,
|
|
'local/use-server-async-exports',
|
|
)
|
|
await expectInvalid(
|
|
'src/app/(campaign)/campanha/actions/futureAction.ts',
|
|
`'use server'\n\nexport { helper } from './futureHelper'\n`,
|
|
'local/use-server-async-exports',
|
|
)
|
|
})
|
|
|
|
it('allows async function and type-only exports from use server modules', async () => {
|
|
await expectValid(
|
|
'src/app/(campaign)/campanha/actions/futureAction.ts',
|
|
`'use server'\nexport type Result = { ok: true }\nexport const save = async () => ({ ok: true as const })\nexport async function load() { return 1 }\n`,
|
|
)
|
|
await expectValid(
|
|
'src/app/(campaign)/campanha/(app)/municipios/municipalityStaffEditMessages.ts',
|
|
`export const municipalityStaffEditSafeMessages = ['x'] as const\n`,
|
|
)
|
|
})
|
|
|
|
it('bans hardcoded ISO fixture dates in e2e specs but not state timestamps', async () => {
|
|
await expectInvalid(
|
|
'tests/e2e/futureFixture.e2e.spec.ts',
|
|
`const fixture = { startAt: '2026-09-01T13:00:00.000Z' }`,
|
|
'no-restricted-syntax',
|
|
)
|
|
await expectInvalid(
|
|
'tests/e2e/futureFixture.e2e.spec.ts',
|
|
`const fixture = { endDate: '2026-09-01T13:00:00.000Z' }`,
|
|
'no-restricted-syntax',
|
|
)
|
|
// `disabledAt`/`lastSyncedAt` are the state under test, not a time window;
|
|
// a computed `startAt` is exactly the sanctioned derivation.
|
|
await expectValid(
|
|
'tests/e2e/futureFixture.e2e.spec.ts',
|
|
`const fixture = { disabledAt: '2026-08-11T00:00:00.000Z' }\nconst startAt = 'dynamic'\nconst activity = { startAt }`,
|
|
)
|
|
})
|
|
})
|