Skip to content

Commit

Permalink
fix(vanilla): Improve Snapshot type so that it respects builtin objec…
Browse files Browse the repository at this point in the history
…ts (#672)

* Improve Snapshot type so that it respects builtin objects

* Update ts 3.4 patch for Snapshot type

* Add pnpm and npm lockfiles to .gitignore. Add tests for Snapshot type.

* Add SnapshotIgnore type to list ignored objects

* Add RegExp, Error, WeakMap and WeakSet to Snapshot typings tests.

* Update yarn.lock

* Update yarn.lock

* Update tests/typings/Snapshot.test-d.ts

Co-authored-by: Daishi Kato <dai-shi@users.noreply.github.com>

* Add test case for classes in Snapshot type tests

* Replace import type with import to fix tests for TS 3.7, because it was introduced in TS 3.8.

* Fix for deep object test in Snapshot tests.

* Fix ESLint error.

* Move Snapshot type tests along the jest tests for snapshot. Rewrite these tests as noot jest tests.

* More Snapshot typings tests into decribe() function.

---------

Co-authored-by: Daishi Kato <dai-shi@users.noreply.github.com>
  • Loading branch information
octet-stream and dai-shi authored Feb 24, 2023
1 parent 0583645 commit 09d7567
Show file tree
Hide file tree
Showing 5 changed files with 555 additions and 401 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,7 @@ yarn-error.log*
# misc
.DS_Store
.idea

# pnpn & npm lockfiles
pnpm-lock.yaml
package-lock.json
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
"test:coverage:watch": "yarn node --experimental-vm-modules $(yarn bin jest) --watch",
"copy": "shx cp -r dist/src/* dist/esm && shx cp -r dist/src/* dist && shx rm -rf dist/{src,tests} && downlevel-dts dist dist/ts3.4 && shx cp package.json readme.md LICENSE dist && json -I -f dist/package.json -e \"this.private=false; this.devDependencies=undefined; this.optionalDependencies=undefined; this.scripts=undefined; this.prettier=undefined; this.jest=undefined;\"",
"patch-macro-vite": "shx cp dist/esm/macro/vite.d.ts dist/macro/ && shx cp dist/ts3.4/esm/macro/vite.d.ts dist/ts3.4/macro/",
"patch-ts3.4": "node -e \"require('shelljs').find('dist/ts3.4/**/*.d.ts').forEach(f=>{require('fs').appendFileSync(f,'declare type Awaited<T> = T extends Promise<infer V> ? V : T;');require('shelljs').sed('-i',/^declare type Snapshot<T> =/,'declare type Snapshot<T> = T extends AnyFunction ? T : T extends AsRef ? T : T extends Promise<any> ? Awaited<T> : { readonly [K in keyof T]: Snapshot2<T[K]> }; type Snapshot2<T> = T extends AnyFunction ? T : T extends AsRef ? T : T extends Promise<any> ? Awaited<T> : { readonly [K in keyof T]: T[K] };declare type _Snapshot<T> =',f)})\"",
"patch-ts3.4": "node -e \"require('shelljs').find('dist/ts3.4/**/*.d.ts').forEach(f=>{require('fs').appendFileSync(f,'declare type Awaited<T> = T extends Promise<infer V> ? V : T;');require('shelljs').sed('-i',/^declare type Snapshot<T> =/,'declare type Snapshot<T> = T extends SnapshotIgnore ? T : T extends Promise<unknown> ? Awaited<T> : T extends object ? { readonly [K in keyof T]: Snapshot2<T[K]> } : T; declare type Snapshot2<T> = T extends SnapshotIgnore ? T : T extends Promise<unknown> ? Awaited<T> : T extends object ? { readonly [K in keyof T]: T[K] } : T;;declare type _Snapshot<T> =',f)})\"",
"patch-esm-ts": "node -e \"require('shelljs').find('dist/esm/**/*.d.ts').forEach(f=>{var f2=f.replace(/\\.ts$/,'.mts');require('fs').copyFileSync(f,f2);require('shelljs').sed('-i',/ from '(\\.[^']+)';$/,' from \\'\\$1.mjs\\';',f2);require('shelljs').sed('-i',/^declare module '(\\.[^']+)'/,'declare module \\'\\$1.mjs\\'',f2)})\""
},
"engines": {
Expand Down Expand Up @@ -185,6 +185,7 @@
"rollup": "^3.15.0",
"rollup-plugin-esbuild": "^5.0.0",
"shx": "^0.3.4",
"ts-expect": "^1.3.0",
"ts-jest": "^29.0.5",
"tslib": "^2.5.0",
"typescript": "^4.9.5",
Expand Down
25 changes: 17 additions & 8 deletions src/vanilla.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { getUntracked, markToTrack } from 'proxy-compare'
const isObject = (x: unknown): x is object =>
typeof x === 'object' && x !== null

type AnyFunction = (...args: any[]) => any

type AsRef = { $$valtioRef: true }

type ProxyObject = object
Expand All @@ -15,17 +17,24 @@ type Op =
| [op: 'reject', path: Path, error: unknown]
type Listener = (op: Op, nextVersion: number) => void

type AnyFunction = (...args: any[]) => any
type SnapshotIgnore =
| Date
| Map<any, any>
| Set<any>
| WeakMap<any, any>
| WeakSet<any>
| AsRef
| Error
| RegExp
| AnyFunction

type Snapshot<T> = T extends AnyFunction
type Snapshot<T> = T extends SnapshotIgnore
? T
: T extends AsRef
? T
: T extends Promise<any>
: T extends Promise<unknown>
? Awaited<T>
: {
readonly [K in keyof T]: Snapshot<T[K]>
}
: T extends object
? { readonly [K in keyof T]: Snapshot<T[K]> }
: T

/**
* This is not a public API.
Expand Down
140 changes: 138 additions & 2 deletions tests/snapshot.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { expect, it } from '@jest/globals'
import { describe, expect, it } from '@jest/globals'
import { createProxy, getUntracked } from 'proxy-compare'
import { proxy, snapshot } from 'valtio'
import { TypeEqual, expectType } from 'ts-expect'
import { INTERNAL_Snapshot as Snapshot, proxy, snapshot } from 'valtio'

const sleep = (ms: number) =>
new Promise((resolve) => {
Expand Down Expand Up @@ -60,3 +61,138 @@ it('should not cause proxy-compare to copy', async () => {
const cmp = createProxy(snap1, new WeakMap())
expect(getUntracked(cmp)).toBe(snap1)
})

describe('snapsoht typings', () => {
it('converts object properties to readonly', () => {
expectType<
TypeEqual<
Snapshot<{
string: string
number: number
null: null
undefined: undefined
bool: boolean
someFunction(): number
ref: {
$$valtioRef: true
}
}>,
{
readonly string: string
readonly number: number
readonly null: null
readonly undefined: undefined
readonly bool: boolean
readonly someFunction: () => number
readonly ref: {
$$valtioRef: true
}
}
>
>(true)
})

it('infers Promise result from property value', () => {
expectType<
TypeEqual<
Snapshot<{ promise: Promise<string> }>,
{ readonly promise: string }
>
>(true)
})

it('converts arrays to readonly arrays', () => {
expectType<TypeEqual<Snapshot<number[]>, readonly number[]>>(true)
})

it('keeps builtin objects from SnapshotIgnore as-is', () => {
expectType<
TypeEqual<
Snapshot<{
date: Date
map: Map<string, unknown>
set: Set<string>
regexp: RegExp
error: Error
weakMap: WeakMap<any, any>
weakSet: WeakSet<any>
}>,
{
readonly date: Date
readonly map: Map<string, unknown>
readonly set: Set<string>
readonly regexp: RegExp
readonly error: Error
readonly weakMap: WeakMap<any, any>
readonly weakSet: WeakSet<any>
}
>
>(true)
})

it('converts collections to readonly', () => {
expectType<
TypeEqual<
Snapshot<{ key: string }[]>,
readonly { readonly key: string }[]
>
>(true)
})

it('convets object properties to readonly recursively', () => {
expectType<
TypeEqual<
Snapshot<{
prevPage: number | null
nextPage: number | null
rows: number
items: {
title: string
details: string | null
createdAt: Date
updatedAt: Date
}[]
}>,
{
readonly prevPage: number | null
readonly nextPage: number | null
readonly rows: number
readonly items: readonly {
readonly title: string
readonly details: string | null
readonly createdAt: Date
readonly updatedAt: Date
}[]
}
>
>(true)
})

it('turns class fields to readonly', () => {
class User {
firstName!: string

lastName!: string

role!: string

hasRole(role: string): boolean {
return this.role === role
}
}

const user = new User()

expectType<
TypeEqual<
Snapshot<typeof user>,
{
readonly firstName: string
readonly lastName: string
readonly role: string
readonly hasRole: (role: string) => boolean
}
>
>(true)
})
})
Loading

1 comment on commit 09d7567

@vercel
Copy link

@vercel vercel bot commented on 09d7567 Feb 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

valtio – ./

valtio-git-main-pmndrs.vercel.app
valtio-pmndrs.vercel.app
valtio.pmnd.rs
valtio.vercel.app

Please sign in to comment.