Files
msd-core/tests/conventional-title.property.test.cjs
Tom Boucher 1fc21cdee0 fix(#2716): route non-user-facing conventional types to an Internal bucket, omit from release notes (#2838)
* test(#2716): failing-first regression for non-user-facing types → Internal bucket

* fix(#2716): route non-user-facing conventional types to an Internal bucket, omit from release notes

* fix(#2716): update SAMPLE_BODY/Discord/property tests for Internal bucket; fix stale CONTRIBUTING sentence (review)

* test(#2716): relax Discord Enhancement assertion (enhance: prefix not stripped by cleanBullet)

* docs(changeset): #2716 non-user-facing types omitted from release notes

* docs(changeset): backfill #2716 PR number to 2838
2026-07-29 17:06:04 -04:00

79 lines
2.8 KiB
JavaScript

'use strict';
/**
* Property-based tests for conventional-title.cjs
*
* Module: scripts/release-notes/conventional-title.cjs
* Exported: evaluatePrTitle({ title }), classifyBucket(title)
*
* Properties tested:
* (a) round-trip: any `type(#n): summary` (type ∈ [a-z]+, n a positive
* integer, non-empty summary) is accepted by the gate. This is the
* generative complement to the hand-picked cases in
* conventional-title.test.cjs — the convention CONTRIBUTING.md asks
* contributors to follow must never be rejected.
* (b) total function: evaluatePrTitle never throws on any string input.
* (c) classifyBucket never throws and always returns one of the 3 buckets.
*/
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const fc = require('./helpers/fast-check-setup.cjs');
const {
evaluatePrTitle,
classifyBucket,
} = require('../scripts/release-notes/conventional-title.cjs');
describe('evaluatePrTitle — properties', () => {
test('(a) any well-formed `type(#n): summary` is accepted', () => {
fc.assert(
fc.property(
// type: a lowercase ascii word, e.g. fix / feat / enhance / chore
fc.stringMatching(/^[a-z]+$/).filter((s) => s.length > 0),
// n: a positive issue number
fc.integer({ min: 1, max: 1_000_000 }),
// summary: non-empty, and not all-whitespace (the title is trimmed,
// but the body after the colon is irrelevant to validity anyway)
fc.string({ minLength: 1 }).filter((s) => s.trim().length > 0),
(type, n, summary) => {
const title = `${type}(#${n}): ${summary}`;
assert.deepEqual(evaluatePrTitle({ title }), { valid: true, reason: 'valid' });
}
)
);
});
test('(b) never throws on arbitrary string input', () => {
fc.assert(
fc.property(fc.string(), (title) => {
const r = evaluatePrTitle({ title });
assert.equal(typeof r.valid, 'boolean');
assert.equal(typeof r.reason, 'string');
})
);
});
test('(b) never throws when called with no argument or a non-string title', () => {
fc.assert(
fc.property(fc.anything(), (title) => {
// evaluatePrTitle coerces title via String(...) — any payload is safe.
const r = evaluatePrTitle({ title });
assert.equal(typeof r.valid, 'boolean');
})
);
assert.equal(evaluatePrTitle().valid, false);
});
});
describe('classifyBucket — properties', () => {
test('(c) always returns one of the three buckets and never throws', () => {
fc.assert(
fc.property(fc.string(), (title) => {
const bucket = classifyBucket(title);
assert.ok(['Feature', 'Fix', 'Enhancement', 'Internal'].includes(bucket));
})
);
});
});