@stjoseph/prumo 0.4.1 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.prumo-templates/core/testing.md +4 -2
- package/.prumo-templates/database/migrations.md +8 -0
- package/package.json +1 -1
- package/templates/api/migrations/package.json +3 -0
- package/templates/api/test/support/global-setup.ts +4 -2
- package/templates/mobile/test/features/profile/profile-form.spec.tsx +5 -1
|
@@ -55,11 +55,13 @@ been published since January 2024. Every other rule here (placement, no database
|
|
|
55
55
|
behaviour) holds unchanged. **What it costs:** two runners in one workspace with near-identical APIs, so `vi.fn`
|
|
56
56
|
written in a mobile test is a mistake an assistant will make.
|
|
57
57
|
|
|
58
|
-
**
|
|
58
|
+
**Four things a mobile test meets, verified with Expo SDK 57:** `render` from `@testing-library/react-native` 14
|
|
59
59
|
is **asynchronous** and its matchers need no setup. `expo-secure-store` needs the native Keychain or Keystore,
|
|
60
60
|
which Jest does not have, so a test builds the auth client with an in-memory token store, which is why a form
|
|
61
61
|
asks only for the auth methods it calls. And `jest-expo` leaves the app manifest empty, so `expo-linking` cannot
|
|
62
|
-
know the app's scheme: a test that reaches `Linking.createURL` mocks it with the scheme it expects.
|
|
62
|
+
know the app's scheme: a test that reaches `Linking.createURL` mocks it with the scheme it expects. A test rendering a `useMutation` builds
|
|
63
|
+
its `QueryClient` with `mutations: { gcTime: Infinity }`: once unmounted, the mutation otherwise starts TanStack
|
|
64
|
+
Query's five-minute garbage-collection timer, and Jest force-exits the worker still waiting on it.
|
|
63
65
|
|
|
64
66
|
A coverage threshold turns a proxy into a target. Whoever is below writes tests to raise the number rather
|
|
65
67
|
than to check anything; whoever is above stops thinking. With no database mocks a test costs more to write,
|
|
@@ -11,6 +11,8 @@ boot, and never by hand.
|
|
|
11
11
|
Make every migration compatible with the **previous** release. Removing a column is two deployments:
|
|
12
12
|
one that stops using it, one that removes it.
|
|
13
13
|
|
|
14
|
+
Keep the `package.json` in the migrations directory. It holds only `"type": "module"`.
|
|
15
|
+
|
|
14
16
|
Delete the generated `down`. Undoing a schema change is a new migration going forward.
|
|
15
17
|
|
|
16
18
|
Keep DDL and data apart. A migration performs DDL only. Filling existing rows is a separate task, run
|
|
@@ -31,6 +33,12 @@ migration fails. It would also mean **the process serving requests holds DDL per
|
|
|
31
33
|
injection or compromise could drop a table. A separate step lets the deploy use a privileged user for
|
|
32
34
|
thirty seconds while the application runs as one that only reads and writes rows.
|
|
33
35
|
|
|
36
|
+
The migrations are TypeScript that Node loads directly, by type stripping; the build never compiles
|
|
37
|
+
them. They use `import`, and without a declared module type Node guesses: it fails to parse them as
|
|
38
|
+
CommonJS, reparses them as ES modules, and warns `MODULE_TYPELESS_PACKAGE_JSON` in every process that
|
|
39
|
+
runs them. The nearest `package.json` decides a file's type, so the one in the migrations directory
|
|
40
|
+
settles it for those files alone and leaves the CommonJS build untouched.
|
|
41
|
+
|
|
34
42
|
During a rolling deploy the previous version is still running against the new schema, so a migration
|
|
35
43
|
that removes something the old code reads breaks production before the new version finishes arriving.
|
|
36
44
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { PostgreSqlContainer, type StartedPostgreSqlContainer } from '@testcontainers/postgresql'
|
|
2
2
|
import type { TestProject } from 'vitest/node'
|
|
3
3
|
|
|
4
|
-
let container: StartedPostgreSqlContainer
|
|
4
|
+
let container: StartedPostgreSqlContainer | undefined
|
|
5
5
|
|
|
6
6
|
export async function setup(project: TestProject): Promise<void> {
|
|
7
7
|
container = await new PostgreSqlContainer('postgres:18-alpine').start()
|
|
@@ -9,8 +9,10 @@ export async function setup(project: TestProject): Promise<void> {
|
|
|
9
9
|
project.provide('postgresUrl', container.getConnectionUri())
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
// Vitest still tears down when setup threw, as when the image could not be pulled; there is no container then, and
|
|
13
|
+
// failing here would print a second error over the one that matters.
|
|
12
14
|
export async function teardown(): Promise<void> {
|
|
13
|
-
await container
|
|
15
|
+
await container?.stop()
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
declare module 'vitest' {
|
|
@@ -36,8 +36,12 @@ describe('ProfileForm', () => {
|
|
|
36
36
|
}),
|
|
37
37
|
})
|
|
38
38
|
|
|
39
|
+
// A mutation left without an observer starts a five-minute garbage-collection timer, and Jest's worker would wait
|
|
40
|
+
// it out. Infinity schedules none, as TanStack Query's testing guide advises.
|
|
41
|
+
const queryClient = new QueryClient({ defaultOptions: { mutations: { gcTime: Infinity } } })
|
|
42
|
+
|
|
39
43
|
await render(
|
|
40
|
-
<QueryClientProvider client={
|
|
44
|
+
<QueryClientProvider client={queryClient}>
|
|
41
45
|
<ProfileForm api={api} profile={profile} />
|
|
42
46
|
</QueryClientProvider>,
|
|
43
47
|
)
|