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
10 changes: 10 additions & 0 deletions scripts/smoke-dist.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,16 @@ for (const [name, mod] of results) {
expected,
`${name}: parse`,
)
// Minifiers rename classes, so `name` must survive in every build.
for (const errorName of expectedExports.filter(e => e.endsWith('Error'))) {
const error = new mod[errorName]('k')
assert.equal(error.name, errorName, `${name}: ${errorName} name`)
assert.match(
String(error),
new RegExp(`^${errorName}: `),
`${name}: ${errorName} toString`,
)
}
assert.doesNotMatch(
readFileSync(new URL(`../${files[name]}`, import.meta.url), 'utf8'),
loadsRemixFile,
Expand Down
57 changes: 57 additions & 0 deletions src/__tests__/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -442,8 +442,65 @@ describe('errors', () => {
formData.append('foo[]', 'test2')
expect(() => parseFormData(formData)).toThrow(new MixedArrayError('foo[]'))
})

it.each([
{
name: 'DuplicateKeyError',
ErrorClass: DuplicateKeyError,
entries: [
['a', '1'],
['a', '2'],
],
depth: Infinity,
},
{
name: 'MixedArrayError',
ErrorClass: MixedArrayError,
entries: [
['a[]', '1'],
['a[0]', '2'],
],
depth: Infinity,
},
{
name: 'ForbiddenKeyError',
ErrorClass: ForbiddenKeyError,
entries: [['__proto__.x', '1']],
depth: Infinity,
},
{
name: 'DepthLimitError',
ErrorClass: DepthLimitError,
entries: [['a.b', '1']],
depth: 1,
},
] satisfies Array<{
name: string
ErrorClass: new (key: string) => Error
entries: Array<[string, string]>
depth: number
}>)('sets the name of $name', ({name, ErrorClass, entries, depth}) => {
const error = errorThrownBy(() => parseFormData(entries, {depth}))
expect(error).toBeInstanceOf(ErrorClass)
expect(error).toBeInstanceOf(Error)
expect(error.name).toBe(name)
expect(String(error)).toBe(`${name}: ${error.message}`)
expect(error.stack).toMatch(new RegExp(`^${name}: `))
expect(new ErrorClass('k').name).toBe(name)
})
})

// The error `fn` throws. Anything else comes back as a plain `Error`, which
// fails the `instanceof` check of the error class.
function errorThrownBy(fn: () => unknown): Error {
try {
fn()
} catch (error) {
return error instanceof Error ? error : new Error('threw a non-Error')
}
return new Error('did not throw')
}

describe('complex examples', () => {
it('can mix array types `foo[$n].foo, -foo[$n].bar, &foo[$n+1] -> {foo: [{foo: value1, bar: null}, Boolean(Number($value3))]}`', () => {
const formData = new FormData()
Expand Down
6 changes: 6 additions & 0 deletions src/errors.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
// The errors `parseFormData` throws. All of them are exported from the package.
// Each class sets `name` as a string literal: minifiers rename the classes, so
// `this.constructor.name` would be `e` or `t` in the minified build.

/**
* Thrown when a path is used multiple times or has mismatching path parts.
Expand Down Expand Up @@ -35,6 +37,7 @@ export class DuplicateKeyError extends Error {
key: string
constructor(key: string) {
super(`Duplicate key at path part ${key}`)
this.name = 'DuplicateKeyError'
this.key = key
}
}
Expand Down Expand Up @@ -64,6 +67,7 @@ export class MixedArrayError extends Error {
key: string
constructor(key: string) {
super(`Mixed array at path part ${key}`)
this.name = 'MixedArrayError'
this.key = key
}
}
Expand Down Expand Up @@ -96,6 +100,7 @@ export class ForbiddenKeyError extends Error {
constructor(key: string) {
key = shortKey(key)
super(`Forbidden key at path part ${key}`)
this.name = 'ForbiddenKeyError'
this.key = key
}
}
Expand All @@ -120,6 +125,7 @@ export class DepthLimitError extends Error {
constructor(key: string) {
key = shortKey(key)
super(`Depth limit exceeded at path part ${key}`)
this.name = 'DepthLimitError'
this.key = key
}
}
Loading