Skip to content

Commit d3af9db

Browse files
committed
Live art test
1 parent ca82304 commit d3af9db

1 file changed

Lines changed: 56 additions & 1 deletion

File tree

test/config.types.test.ts

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import test from 'ava'
2-
import { SettingDefinition, Settings } from '../src/config'
2+
import { SettingsDefinitionMap, SettingDefinition, Settings } from '../src/config'
33

44
// This file has type declarations that test the types of the config system at
55
// compile time.
@@ -157,6 +157,61 @@ const _settingsType: ExpectEqual<
157157
> = true
158158
void _settingsType
159159

160+
// Reproduces the bug from `packages/sources/liveart/src/config/index.ts`.
161+
// Annotating the variable with the default `SettingsDefinitionMap` (no
162+
// inferred generic) widens the settings map to `Record<string,
163+
// SettingDefinition>`. Any custom setting then resolves via an index
164+
// signature whose value type is the full `SettingType<SettingDefinition>`
165+
// union.
166+
//
167+
// Previously this collapsed to `undefined` (because `SettingType` was not
168+
// distributive over `SettingDefinition`), which silently allowed values
169+
// like `adapterSettings.API_BASE_URL` to be passed into stricter types
170+
// such as Axios's `baseURL: string | undefined`.
171+
const wideConfig: SettingsDefinitionMap = {
172+
API_BASE_URL: {
173+
type: 'string',
174+
description: 'API base URL',
175+
required: true,
176+
default: 'https://example.com',
177+
},
178+
}
179+
void wideConfig
180+
181+
const _wideConfigAccess: ExpectEqual<
182+
Settings<typeof wideConfig>['API_BASE_URL'],
183+
string | number | boolean | undefined
184+
> = true
185+
void _wideConfigAccess
186+
187+
// The tests below are not directly a test of the config types, but it
188+
// demonstrates an important principle used in SettingType.
189+
190+
type MyType = { foo: number } | { foo: string }
191+
192+
type TypeOfFoo<T extends { foo: unknown }> = T['foo'] extends string
193+
? string
194+
: T['foo'] extends number
195+
? number
196+
: never
197+
198+
type DistributeTypeOfFoo<T extends { foo: unknown }> = T extends never ? never : TypeOfFoo<T>
199+
200+
// If TypeOfFoo is applied directly to MyType, it will evaluate T['foo'] as
201+
// `string | number`, which does not satisfy either condition and results in
202+
// `never`.
203+
const _testTypeOfFooDirectly: ExpectEqual<TypeOfFoo<MyType>, never> = true
204+
void _testTypeOfFooDirectly
205+
206+
// But if you do the same inside a seemingly useless conditional, it will apply
207+
// `TypeOfFoo` to each member of the union separately (first to
208+
// `{ foo: number }` and then to `{ foo: string }`), and then combine the
209+
// results together. So it will evaluate to `number` for the first member and
210+
// `string` for the second member, and the result will be `number | string`.
211+
// then unions the results together.
212+
const _testTypeOfFooDistributed: ExpectEqual<DistributeTypeOfFoo<MyType>, string | number> = true
213+
void _testTypeOfFooDistributed
214+
160215
test('add one actual test so the test framework does not complain', (t) => {
161216
t.pass()
162217
})

0 commit comments

Comments
 (0)