Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 68 additions & 1 deletion src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import {
} from '../'
import {outcomeWithName} from './utils'

// The tests run on Node, but the project has no `@types/node`.
declare const process: {env: Record<string, string | undefined>}

describe('basic functionality', () => {
describe('transform value', () => {
it('can parse Numbers `+foo -> {foo: Number($value)}`', () => {
Expand All @@ -28,6 +31,14 @@ describe('basic functionality', () => {
foo: false,
})
})
it('can parse true Booleans `&foo: true -> {foo: true}`', () => {
const formData = new FormData()
formData.append('&foo', 'true')
const result = parseFormData(formData)
expect(result).toEqual({
foo: true,
})
})
it("can parse checkbox Booleans `&foo: on -> {foo: $value === 'on'}`", () => {
const formData = new FormData()
formData.append('&foo', 'on')
Expand Down Expand Up @@ -801,7 +812,10 @@ describe('bracket keys', () => {

// Each shape needs the matching `]` of every `[`. Searching for it again
// for every `[` takes seconds at this length.
it.each([
// Skipped during mutation testing: Stryker's instrumented code runs ~4x
// slower on all CPU cores at once, so these tests fail without a mutant and
// "kill" whichever mutant is active.
it.skipIf(process.env['STRYKER_MUTATOR_WORKER'] !== undefined).each([
['a[b]', 'x'],
['a[', ''],
['a[0]b', ''],
Expand Down Expand Up @@ -1013,6 +1027,36 @@ describe('Object.prototype names', () => {
expect(result).toStrictEqual({})
})

// A removed entry can go through an empty object or array that a kept entry
// set (only possible with such a transform). Undoing the removed entry must
// not delete it.
it.each<[string, unknown, string, unknown]>([
['a', {}, 'a.toString', {a: {}}],
['a', [], 'a[].toString', {a: []}],
])(
'keeps %s with the value %j when %s is removed',
(keptPath, keptValue, removedPath, expected) => {
const values = new Map([
[keptPath, keptValue],
[removedPath, 'x'],
])
const result = parseFormData(
[
[keptPath, 'x'],
[removedPath, 'x'],
],
{
transformEntry: ([path]) => ({
path,
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- see above
value: values.get(path) as string,
}),
},
)
expect(result).toStrictEqual(expected)
},
)

// The same errors as for an unused name, so an error doesn't tell the
// client which names exist on Object.prototype.
it.each([
Expand Down Expand Up @@ -1090,6 +1134,29 @@ describe('Object.prototype names', () => {
expect(parseFormData([['a.patched', '1']])).toStrictEqual({})
})
})

// Only object path parts are compared with Object.prototype. `a[]` has no
// object key, so its entries stay.
describe('an empty-string property added to Object.prototype', () => {
const proto: object = Object.prototype
afterEach(() => {
Reflect.deleteProperty(proto, '')
})

it('keeps the entries of `a[]`', () => {
Object.defineProperty(proto, '', {
value: 'x',
configurable: true,
writable: true,
})
expect(
parseFormData([
['a[]', 'x'],
['a[]', 'y'],
]),
).toStrictEqual({a: ['x', 'y']})
})
})
})

class RemixFileSubclass extends File {}
Expand Down
11 changes: 7 additions & 4 deletions stryker.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ const config = {
testRunner: 'vitest',
mutate: ['src/index.ts', 'src/path-parts.ts', 'src/transform.ts'],
reporters: ['html', 'clear-text', 'progress'],
// The first runs scored 95.0-97.0%. The score moves because the tests that
// parse 200 000-character paths in under 500 ms sometimes fail under
// Stryker's load and so kill a mutant. `break` leaves room for that.
thresholds: {high: 97, low: 95, break: 93},
// The score is 96.37% (292 of 303 mutants) in every run. The 11 surviving
// mutants are equivalent: they can't change any output. `// Stryker
// disable` comments can't exclude them, because a comment disables every
// mutant of that mutator on the line, including ones the tests kill.
// `break` is just under the score, so one new surviving mutant
// (291 of 303 = 96.04%) fails the run. Raise it when the score goes up.
thresholds: {high: 100, low: 95, break: 96.3},
// Stryker rewrites `extends`/`references` paths in this file with the
// TypeScript JS API, which TypeScript 7 doesn't ship. Our tsconfig.json has
// neither, so we point Stryker at a file that doesn't exist and it skips
Expand Down
Loading