Convert tests to Vitest (#4)

This commit is contained in:
Darius
2022-01-11 00:33:13 -06:00
committed by GitHub
parent 661a253d8c
commit 5b2db4dd69
72 changed files with 544 additions and 1984 deletions

View File

@@ -17,9 +17,9 @@
},
"overrides": [
{
"files": ["packages/docs/cypress/**"],
"files": ["packages/website/cypress/**"],
"parserOptions": {
"project": "./packages/docs/cypress/tsconfig.json"
"project": "./packages/website/cypress/tsconfig.json"
}
}
]

View File

@@ -1,11 +1,10 @@
name: deploy docs
name: deploy website
on:
push:
branches: [main]
paths:
- "packages/docs/**"
- "packages/website/**"
- "reacord/library/**/*.{ts,tsx}"
jobs:
deploy:
runs-on: ubuntu-latest

View File

@@ -11,20 +11,24 @@ env:
TEST_GUILD_ID: ${{ secrets.TEST_GUILD_ID }}
jobs:
run-scripts:
run-commands:
strategy:
matrix:
scripts:
- name: test
script: coverage
- name: lint
script: lint
- name: typecheck
script: typecheck
- name: build
script: build
fail-fast: false
name: ${{ matrix.scripts.name }}
matrix:
command:
# if these run in the same process, it dies,
# so we test them separate
- name: test reacord
run: pnpm test -C packages/reacord
- name: test website
run: pnpm test -C packages/website
- name: build
run: pnpm build --recursive
- name: lint
run: pnpm lint
- name: typecheck
run: pnpm typecheck --parallel
name: ${{ matrix.command.name }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
@@ -34,4 +38,4 @@ jobs:
node-version: "16"
- run: npm i -g pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm run --recursive ${{ matrix.scripts.script }}
- run: ${{ matrix.command.run }}

View File

@@ -9,7 +9,7 @@ RUN ls -R
RUN npm install -g pnpm
RUN pnpm install --unsafe-perm --frozen-lockfile
RUN pnpm run build -C packages/docs
RUN pnpm run build -C packages/website
ENV NODE_ENV=production
CMD [ "pnpm", "-C", "packages/docs", "start" ]
CMD [ "pnpm", "-C", "packages/website", "start" ]

View File

@@ -9,8 +9,8 @@
"@itsmapleleaf/configs": "^1.1.2"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.9.0",
"@typescript-eslint/parser": "^5.9.0",
"@typescript-eslint/eslint-plugin": "^5.9.1",
"@typescript-eslint/parser": "^5.9.1",
"eslint": "^8.6.0",
"eslint-config-prettier": "^8.3.0",
"eslint-import-resolver-typescript": "^2.5.0",
@@ -18,7 +18,7 @@
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-react": "^7.28.0",
"eslint-plugin-react-hooks": "^4.3.0",
"eslint-plugin-unicorn": "^39.0.0",
"eslint-plugin-unicorn": "^40.0.0",
"prettier": "^2.5.1",
"typescript": "^4.5.4"
},

View File

@@ -2,6 +2,7 @@ import type {
CamelCasedPropertiesDeep,
SnakeCasedPropertiesDeep,
} from "type-fest"
import { expect, test } from "vitest"
import { camelCaseDeep, snakeCaseDeep } from "./convert-object-property-case"
test("camelCaseDeep", () => {
@@ -12,12 +13,14 @@ test("camelCaseDeep", () => {
someOtherProp: "someOtherValue",
}
expect(camelCaseDeep(input)).toEqual<CamelCasedPropertiesDeep<typeof input>>({
const expected: CamelCasedPropertiesDeep<typeof input> = {
someProp: {
someDeepProp: "some_deep_value",
},
someOtherProp: "someOtherValue",
})
}
expect(camelCaseDeep(input)).toEqual(expected)
})
test("snakeCaseDeep", () => {
@@ -28,10 +31,12 @@ test("snakeCaseDeep", () => {
some_other_prop: "someOtherValue",
}
expect(snakeCaseDeep(input)).toEqual<SnakeCasedPropertiesDeep<typeof input>>({
const expected: SnakeCasedPropertiesDeep<typeof input> = {
some_prop: {
some_deep_prop: "someDeepValue",
},
some_other_prop: "someOtherValue",
})
}
expect(snakeCaseDeep(input)).toEqual(expected)
})

View File

@@ -0,0 +1,7 @@
export function isObject<T>(
value: T,
): value is Exclude<T, Primitive | AnyFunction> {
return typeof value === "object" && value !== null
}
type Primitive = string | number | boolean | undefined | null
type AnyFunction = (...args: any[]) => any

View File

@@ -1,15 +0,0 @@
export function pruneNullishValues<T extends object>(
object: T,
): PruneNullishValues<T> {
const result: any = {}
for (const [key, value] of Object.entries(object)) {
if (value != undefined) {
result[key] = value
}
}
return result
}
type PruneNullishValues<T> = {
[Key in keyof T]: NonNullable<T[Key]>
}

View File

@@ -0,0 +1,27 @@
import { isObject } from "./is-object"
export function pruneNullishValues<T>(input: T): PruneNullishValues<T> {
if (Array.isArray(input)) {
return input.filter(Boolean).map((item) => pruneNullishValues(item)) as any
}
if (!isObject(input)) {
return input as any
}
const result: any = {}
for (const [key, value] of Object.entries(input)) {
if (value != undefined) {
result[key] = isObject(value) ? pruneNullishValues(value) : value
}
}
return result
}
type PruneNullishValues<Input> = Input extends ReadonlyArray<infer Value>
? ReadonlyArray<NonNullable<Value>>
: Input extends object
? {
[Key in keyof Input]: NonNullable<Input[Key]>
}
: Input

View File

@@ -6,5 +6,5 @@ export async function rejectAfter(
error: unknown = `rejected after ${timeMs}ms`,
): Promise<never> {
await setTimeout(timeMs)
return Promise.reject(toError(error))
throw toError(error)
}

View File

@@ -1,15 +0,0 @@
/** @type {import('@jest/types').Config.InitialOptions} */
const config = {
transform: {
"^.+\\.[jt]sx?$": ["esbuild-jest", { format: "esm", sourcemap: true }],
},
extensionsToTreatAsEsm: [".ts", ".tsx"],
moduleNameMapper: {
"^(\\.\\.?/.+)\\.jsx?$": "$1",
},
verbose: true,
cache: false,
coverageReporters: ["text", "text-summary", "html"],
coveragePathIgnorePatterns: ["discord-js-adapter", "test/setup-testing"],
}
export default config

View File

@@ -4,7 +4,11 @@ import React from "react"
import { isInstanceOf } from "../../../helpers/is-instance-of"
import { ReacordElement } from "../../internal/element.js"
import type { ComponentInteraction } from "../../internal/interaction"
import type { ActionRow, MessageOptions } from "../../internal/message"
import type {
ActionRow,
ActionRowItem,
MessageOptions,
} from "../../internal/message"
import { Node } from "../../internal/node.js"
import type { ComponentEvent } from "../component-event"
import { OptionNode } from "./option-node"
@@ -108,19 +112,25 @@ class SelectNode extends Node<SelectProps> {
...props
} = this.props
actionRow.push({
const item: ActionRowItem = {
...props,
type: "select",
customId: this.customId,
options,
// I'm not counting on people using value and values at the same time,
// but maybe we should resolve this differently anyhow? e.g. one should override the other
// or just warn if there are both
// or... try some other alternative design entirely
values: [...(values || []), ...(value ? [value] : [])],
minValues: multiple ? minValues : undefined,
maxValues: multiple ? Math.max(minValues, maxValues) : undefined,
})
values: [],
}
if (multiple) {
item.minValues = minValues
item.maxValues = maxValues
if (values) item.values = values
}
if (!multiple && value != undefined) {
item.values = [value]
}
actionRow.push(item)
}
override handleComponentInteraction(

View File

@@ -3,7 +3,7 @@ import * as Discord from "discord.js"
import type { ReactNode } from "react"
import type { Except } from "type-fest"
import { pick } from "../../helpers/pick"
import { pruneNullishValues } from "../../helpers/prune-null-values"
import { pruneNullishValues } from "../../helpers/prune-nullish-values"
import { raise } from "../../helpers/raise"
import { toUpper } from "../../helpers/to-upper"
import type { ComponentInteraction } from "../internal/interaction"

View File

@@ -9,9 +9,12 @@ export type MessageOptions = {
actionRows: ActionRow[]
}
export type ActionRow = Array<
MessageButtonOptions | MessageLinkOptions | MessageSelectOptions
>
export type ActionRow = ActionRowItem[]
export type ActionRowItem =
| MessageButtonOptions
| MessageLinkOptions
| MessageSelectOptions
export type MessageButtonOptions = {
type: "button"

View File

@@ -15,4 +15,3 @@ export * from "./core/components/select"
export * from "./core/instance"
export * from "./core/reacord"
export * from "./core/reacord-discord-js"
export * from "./core/reacord-tester"

View File

@@ -20,9 +20,8 @@
"scripts": {
"build": "tsup-node library/main.ts --target node16 --format cjs,esm --dts --sourcemap",
"build-watch": "pnpm build -- --watch",
"test": "node --experimental-vm-modules --no-warnings ./node_modules/jest/bin/jest.js --colors",
"test-watch": "pnpm test -- --watch",
"coverage": "pnpm test -- --coverage",
"test": "vitest --coverage --no-watch",
"test-dev": "vitest",
"typecheck": "tsc --noEmit",
"playground": "nodemon --exec esmo --ext ts,tsx ./playground/main.tsx",
"release": "release-it"
@@ -33,7 +32,7 @@
"@types/react-reconciler": "^0.26.4",
"nanoid": "^3.1.30",
"react-reconciler": "^0.26.2",
"rxjs": "^7.5.1"
"rxjs": "^7.5.2"
},
"peerDependencies": {
"discord.js": "^13.3",
@@ -45,8 +44,6 @@
}
},
"devDependencies": {
"@jest/globals": "^27.4.6",
"@types/jest": "^27.4.0",
"@types/lodash-es": "^4.17.5",
"c8": "^7.11.0",
"discord.js": "^13.5.1",
@@ -54,7 +51,6 @@
"esbuild": "latest",
"esbuild-jest": "^0.5.0",
"esmo": "^0.13.0",
"jest": "^27.4.7",
"lodash-es": "^4.17.21",
"nodemon": "^2.0.15",
"prettier": "^2.5.1",
@@ -63,7 +59,9 @@
"release-it": "^14.12.1",
"tsup": "^5.11.11",
"type-fest": "^2.9.0",
"typescript": "^4.5.4"
"typescript": "^4.5.4",
"vite": "^2.7.10",
"vitest": "^0.0.140"
},
"resolutions": {
"esbuild": "latest"

View File

@@ -1,6 +1,7 @@
import React from "react"
import { ReacordTester } from "../library/core/reacord-tester"
import { test } from "vitest"
import { ActionRow, Button, Select } from "../library/main"
import { ReacordTester } from "./test-adapter"
const testing = new ReacordTester()

View File

@@ -1,2 +1,3 @@
import { test } from "vitest"
test.todo("channel message renderer")
export {}

View File

@@ -1,2 +1,3 @@
import { test } from "vitest"
test.todo("discord js integration")
export {}

View File

@@ -1,5 +1,5 @@
import React from "react"
import { ReacordTester } from "../library/core/reacord-tester"
import { test } from "vitest"
import {
Embed,
EmbedAuthor,
@@ -9,6 +9,7 @@ import {
EmbedThumbnail,
EmbedTitle,
} from "../library/main"
import { ReacordTester } from "./test-adapter"
const testing = new ReacordTester()

View File

@@ -1,2 +1,2 @@
import { test } from "vitest"
test.todo("ephemeral reply")
export {}

View File

@@ -1,3 +1,4 @@
import { test } from "vitest"
test.todo("button onClick")
test.todo("select onChange")
export {}

View File

@@ -1,6 +1,7 @@
import React from "react"
import { ReacordTester } from "../library/core/reacord-tester"
import { test } from "vitest"
import { Link } from "../library/main"
import { ReacordTester } from "./test-adapter"
const tester = new ReacordTester()

View File

@@ -1,11 +1,7 @@
import * as React from "react"
import {
Button,
Embed,
EmbedField,
EmbedTitle,
ReacordTester,
} from "../library/main"
import { test } from "vitest"
import { Button, Embed, EmbedField, EmbedTitle } from "../library/main"
import { ReacordTester } from "./test-adapter"
test("rendering behavior", async () => {
const tester = new ReacordTester()

View File

@@ -1,6 +1,6 @@
import { test } from "vitest"
// test that the interaction update is _eventually_ deferred if there's no component update,
// and that update isn't called after the fact
// ...somehow
test.todo("defer update timeout")
export {}

View File

@@ -1,10 +1,11 @@
import { jest } from "@jest/globals"
import React, { useState } from "react"
import { Button, Option, ReacordTester, Select } from "../library/main"
import { expect, fn, test } from "vitest"
import { Button, Option, Select } from "../library/main"
import { ReacordTester } from "./test-adapter"
test("single select", async () => {
const tester = new ReacordTester()
const onSelect = jest.fn()
const onSelect = fn()
function TestSelect() {
const [value, setValue] = useState<string>()
@@ -74,7 +75,7 @@ test("single select", async () => {
test("multiple select", async () => {
const tester = new ReacordTester()
const onSelect = jest.fn()
const onSelect = fn()
function TestSelect() {
const [values, setValues] = useState<string[]>([])
@@ -125,13 +126,13 @@ test("multiple select", async () => {
expect(onSelect).toHaveBeenCalledTimes(0)
tester.findSelectByPlaceholder("select").select("1", "3")
await assertSelect(expect.arrayContaining(["1", "3"]))
await assertSelect(expect.arrayContaining(["1", "3"]) as unknown as string[])
expect(onSelect).toHaveBeenCalledWith(
expect.objectContaining({ values: expect.arrayContaining(["1", "3"]) }),
)
tester.findSelectByPlaceholder("select").select("2")
await assertSelect(expect.arrayContaining(["2"]))
await assertSelect(expect.arrayContaining(["2"]) as unknown as string[])
expect(onSelect).toHaveBeenCalledWith(
expect.objectContaining({ values: expect.arrayContaining(["2"]) }),
)

View File

@@ -4,34 +4,36 @@ import { nanoid } from "nanoid"
import { nextTick } from "node:process"
import { promisify } from "node:util"
import type { ReactNode } from "react"
import { logPretty } from "../../helpers/log-pretty"
import { omit } from "../../helpers/omit"
import { raise } from "../../helpers/raise"
import type { Channel } from "../internal/channel"
import { Container } from "../internal/container"
import type {
ButtonInteraction,
CommandInteraction,
SelectInteraction,
} from "../internal/interaction"
import type {
Message,
MessageButtonOptions,
MessageOptions,
MessageSelectOptions,
} from "../internal/message"
import { ChannelMessageRenderer } from "../internal/renderers/channel-message-renderer"
import { InteractionReplyRenderer } from "../internal/renderers/interaction-reply-renderer"
import { expect } from "vitest"
import { logPretty } from "../helpers/log-pretty"
import { omit } from "../helpers/omit"
import { pruneNullishValues } from "../helpers/prune-nullish-values"
import { raise } from "../helpers/raise"
import type {
ChannelInfo,
GuildInfo,
MessageInfo,
UserInfo,
} from "./component-event"
import type { ButtonClickEvent } from "./components/button"
import type { SelectChangeEvent } from "./components/select"
import type { ReacordInstance } from "./instance"
import { Reacord } from "./reacord"
} from "../library/core/component-event"
import type { ButtonClickEvent } from "../library/core/components/button"
import type { SelectChangeEvent } from "../library/core/components/select"
import type { ReacordInstance } from "../library/core/instance"
import { Reacord } from "../library/core/reacord"
import type { Channel } from "../library/internal/channel"
import { Container } from "../library/internal/container"
import type {
ButtonInteraction,
CommandInteraction,
SelectInteraction,
} from "../library/internal/interaction"
import type {
Message,
MessageButtonOptions,
MessageOptions,
MessageSelectOptions,
} from "../library/internal/message"
import { ChannelMessageRenderer } from "../library/internal/renderers/channel-message-renderer"
import { InteractionReplyRenderer } from "../library/internal/renderers/interaction-reply-renderer"
const nextTickPromise = promisify(nextTick)
@@ -87,14 +89,21 @@ export class ReacordTester extends Reacord {
}
sampleMessages() {
return this.messages.map((message) => ({
...message.options,
actionRows: message.options.actionRows.map((row) =>
row.map((component) =>
omit(component, ["customId", "onClick", "onSelect", "onSelectValue"]),
return pruneNullishValues(
this.messages.map((message) => ({
...message.options,
actionRows: message.options.actionRows.map((row) =>
row.map((component) =>
omit(component, [
"customId",
"onClick",
"onSelect",
"onSelectValue",
]),
),
),
),
}))
})),
)
}
findButtonByLabel(label: string) {

View File

@@ -0,0 +1,8 @@
/// <reference types="vitest" />
import { defineConfig } from "vite"
export default defineConfig({
build: {
sourcemap: true,
},
})

View File

@@ -5,3 +5,5 @@ node_modules
/public/build
.env
/public/api
cypress/videos
cypress/screenshots

View File

@@ -16,7 +16,7 @@
* @type {Cypress.PluginConfig}
*/
// eslint-disable-next-line no-unused-vars
// module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
// }
export default function cypressConfig(on, config) {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
}

View File

@@ -1,10 +1,10 @@
{
"private": true,
"name": "reacord-docs-new",
"name": "website",
"scripts": {
"prepare": "remix setup node",
"dev": "concurrently 'typedoc --watch' 'remix dev'",
"test": "pnpm build && pnpm start & wait-on http-get://localhost:3000 && cypress run",
"test": "node ./scripts/test.js",
"test-dev": "pnpm dev & wait-on http-get://localhost:3000 && cypress open",
"build": "typedoc && remix build",
"start": "remix-serve build",
@@ -35,9 +35,11 @@
"@types/react": "^17.0.38",
"@types/react-dom": "^17.0.11",
"@types/tailwindcss": "^3.0.2",
"@types/wait-on": "^5.3.1",
"concurrently": "^7.0.0",
"cypress": "^9.2.0",
"rehype-prism-plus": "^1.2.2",
"cypress": "^9.2.1",
"execa": "^6.0.0",
"rehype-prism-plus": "^1.3.0",
"typedoc": "^0.22.10",
"typescript": "^4.5.4",
"wait-on": "^6.0.0"

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,3 @@
{
"type": "module"
}

View File

@@ -0,0 +1,10 @@
import cypress from "cypress"
import { execa } from "execa"
import waitOn from "wait-on"
await execa("pnpm", ["build"], { stdio: "inherit" })
const app = execa("pnpm", ["start"], { stdio: "inherit" })
await waitOn({ resources: ["http-get://localhost:3000"] })
await cypress.run()
console.log("cypress run done")
app.kill()

2187
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff