Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

atom.io/solid #2534

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
13 changes: 13 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,19 @@
"smartStep": true,
"console": "integratedTerminal"
},
{
"type": "node",
"request": "launch",
"name": "Debug Current Test File [Built]",
"autoAttachChildProcesses": true,
"skipFiles": ["<node_internals>/**", "**/node_modules/**"],
"cwd": "${workspaceRoot}/packages/atom.io",
"program": "${workspaceRoot}/node_modules/vitest/vitest.mjs",
"env": { "IMPORT": "dist" },
"args": ["run", "${fileBasenameNoExtension}"],
"smartStep": true,
"console": "integratedTerminal"
},
{
"type": "bun",
"request": "launch",
Expand Down
29 changes: 29 additions & 0 deletions packages/atom.io/__scripts__/finish-solid-build.node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import * as fs from "node:fs"
import * as path from "node:path"

import { ATOM_IO_ROOT } from "./constants"

function replacePhraseInFile(
filePath: string,
oldPhrase: string,
newPhrase: string,
): void {
try {
const data = fs.readFileSync(filePath, `utf8`)

const result = data.replace(new RegExp(oldPhrase, `g`), newPhrase)

fs.writeFileSync(filePath, result, `utf8`)

console.log(
`Replaced all instances of "${oldPhrase}" with "${newPhrase}" in file "${filePath}"`,
)
} catch (error) {
console.error(`Error processing file "${filePath}":`, error)
}
}

for (const filename of [`index.js`, `index.cjs`]) {
const filePath = path.join(ATOM_IO_ROOT, `solid/dist`, filename)
replacePhraseInFile(filePath, `solid-js/jsx-runtime`, `solid-js/h/jsx-runtime`)
}
279 changes: 279 additions & 0 deletions packages/atom.io/__tests__/public/hooks.solid.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
/** @jsxImportSource solid-js */
/** @jsx preserve */
/** @jsxFrag Fragment */

import { fireEvent, prettyDOM, render, waitFor } from "@solidjs/testing-library"
import type { Logger, TimelineToken } from "atom.io"
import { atom, redo, selector, timeline, undo } from "atom.io"
import * as Internal from "atom.io/internal"
import * as AS from "atom.io/solid"
import { type Component, createMemo, type JSX } from "solid-js"

import * as Utils from "../__util__"

const LOG_LEVELS = [null, `error`, `warn`, `info`] as const
const CHOOSE = 2

let logger: Logger

beforeEach(() => {
Internal.clearStore(Internal.IMPLICIT.STORE)
Internal.IMPLICIT.STORE.loggers[0].logLevel = LOG_LEVELS[CHOOSE]
logger = Internal.IMPLICIT.STORE.logger
vitest.spyOn(logger, `error`)
vitest.spyOn(logger, `warn`)
vitest.spyOn(logger, `info`)
vitest.spyOn(Utils, `stdout`)
})

describe(`single atom`, () => {
const setters: Internal.Func[] = []
const scenario = () => {
const letterState = atom<string>({
key: `letter`,
default: `A`,
})
function Letter(): JSX.Element {
const setLetter = AS.useI(letterState)
const letter = AS.useO(letterState)
setters.push(setLetter)
return (
<div>
<div data-testid={letter()}>{letter()}</div>
<button
type="button"
onClick={() => {
setLetter(`B`)
}}
data-testid="changeStateButton"
/>
</div>
)
}
const utils = render(() => (
<AS.StoreProvider>
<Letter />
</AS.StoreProvider>
))
console.log(prettyDOM(document))
return { ...utils }
}

it(`accepts user input with externally managed state`, () => {
const { getByTestId } = scenario()
const changeStateButton = getByTestId(`changeStateButton`)
fireEvent.click(changeStateButton)
const option = getByTestId(`B`)
expect(option).toBeTruthy()
expect(setters.length).toBe(1)
})
})
describe(`timeline`, () => {
const setters: Internal.Func[] = []
const scenario = () => {
const letterState = atom<string>({
key: `letter`,
default: `A`,
})
const letterTL = timeline({
key: `letterTL`,
scope: [letterState],
})
const Letter: Component = () => {
const setLetter = AS.useI(letterState)
const letter = AS.useO(letterState)
const letterTimeline = AS.useTL(letterTL)
setters.push(setLetter)
return (
<>
<div data-testid={letter()}>{letter()}</div>
<div data-testid="timelineAt">{letterTimeline.at()}</div>
<div data-testid="timelineLength">{letterTimeline.length()}</div>
<button
type="button"
onClick={() => {
setLetter(`B`)
}}
data-testid="changeStateButtonB"
/>
<button
type="button"
onClick={() => {
setLetter(`C`)
}}
data-testid="changeStateButtonC"
/>
<button
type="button"
onClick={() => {
letterTimeline.undo()
}}
data-testid="undoButton"
/>
<button
type="button"
onClick={() => {
letterTimeline.redo()
}}
data-testid="redoButton"
/>
</>
)
}
const utils = render(() => (
<AS.StoreProvider>
<Letter />
</AS.StoreProvider>
))
return { ...utils, letterTL }
}

it(`displays metadata`, async () => {
const { getByTestId, letterTL } = scenario()
const changeStateButtonB = getByTestId(`changeStateButtonB`)
const changeStateButtonC = getByTestId(`changeStateButtonC`)
fireEvent.click(changeStateButtonB)
const option = getByTestId(`B`)
expect(option).toBeTruthy()
await waitFor(() => getByTestId(`timelineAt`).textContent === `1`)
const timelineAt = getByTestId(`timelineAt`)
expect(timelineAt.textContent).toEqual(`1`)
const timelineLength = getByTestId(`timelineLength`)
expect(timelineLength.textContent).toEqual(`1`)
fireEvent.click(changeStateButtonC)
const option2 = getByTestId(`C`)
expect(option2).toBeTruthy()
expect(timelineAt.textContent).toEqual(`2`)
undo(letterTL)
expect(timelineAt.textContent).toEqual(`1`)
expect(timelineLength.textContent).toEqual(`2`)
redo(letterTL)
expect(timelineAt.textContent).toEqual(`2`)
expect(timelineLength.textContent).toEqual(`2`)
const undoButton = getByTestId(`undoButton`)
fireEvent.click(undoButton)
expect(timelineAt.textContent).toEqual(`1`)
expect(timelineLength.textContent).toEqual(`2`)
const redoButton = getByTestId(`redoButton`)
fireEvent.click(redoButton)
expect(timelineAt.textContent).toEqual(`2`)
expect(timelineLength.textContent).toEqual(`2`)
})
})
describe(`timeline (dynamic)`, () => {
const scenario = () => {
const letterState = atom<string>({
key: `letter`,
default: `A`,
})
const numberState = atom<number>({
key: `number`,
default: 1,
})
const letterTL = timeline({
key: `letterTL`,
scope: [letterState],
})
const numberTL = timeline({
key: `numberTL`,
scope: [numberState],
})
const whichTimelineState = atom<string>({
key: `whichTimeline`,
default: `letter`,
})
const timelineState = selector<TimelineToken<unknown>>({
key: `timeline`,
get: ({ get }) => {
const whichTimeline = get(whichTimelineState)
return whichTimeline === `letter` ? letterTL : numberTL
},
})
const Letter: Component = () => {
const setLetter = AS.useI(letterState)
const setNumber = AS.useI(numberState)
const setWhichTimeline = AS.useI(whichTimelineState)
const letter = AS.useO(letterState)
const number = AS.useO(numberState)
const tl = createMemo(() => AS.useTL(AS.useO(timelineState)()))
return (
<>
<div data-testid={letter()}>{letter()}</div>
<div data-testid={number()}>{number()}</div>
<div data-testid="timelineAt">{tl().at()}</div>
<div data-testid="timelineLength">{tl().length()}</div>
<button
type="button"
onClick={() => {
setLetter(`B`)
}}
data-testid="changeLetterButtonB"
/>
<button
type="button"
onClick={() => {
setNumber(2)
}}
data-testid="changeNumberButton2"
/>
<button
type="button"
onClick={() => {
setWhichTimeline((current) =>
current === `number` ? `letter` : `number`,
)
}}
data-testid="changeTimelineButton"
/>
<button
type="button"
onClick={() => {
tl().undo()
}}
data-testid="undoButton"
/>
<button
type="button"
onClick={() => {
tl().redo()
}}
data-testid="redoButton"
/>
</>
)
}
// const TimelineData: Component<{ timeline: TimelineData } = () => {}
const utils = render(() => (
<AS.StoreProvider>
<Letter />
</AS.StoreProvider>
))
return { ...utils, letterTL }
}

it(`displays metadata`, () => {
const { getByTestId, letterTL } = scenario()
const changeLetterButtonB = getByTestId(`changeLetterButtonB`)
const changeNumberButton2 = getByTestId(`changeNumberButton2`)
const changeTimelineButton = getByTestId(`changeTimelineButton`)
const timelineAt = getByTestId(`timelineAt`)
const timelineLength = getByTestId(`timelineLength`)
fireEvent.click(changeLetterButtonB)
const option = getByTestId(`B`)
expect(option).toBeTruthy()
expect(timelineAt.textContent).toEqual(`1`)
expect(timelineLength.textContent).toEqual(`1`)
fireEvent.click(changeTimelineButton)
expect(timelineAt.textContent).toEqual(`0`)
expect(timelineLength.textContent).toEqual(`0`)
fireEvent.click(changeNumberButton2)
const option2 = getByTestId(`2`)
expect(option2).toBeTruthy()
expect(timelineAt.textContent).toEqual(`1`)
expect(timelineLength.textContent).toEqual(`1`)
undo(letterTL)
fireEvent.click(changeTimelineButton)
expect(timelineAt.textContent).toEqual(`0`)
expect(timelineLength.textContent).toEqual(`1`)
})
})
1 change: 1 addition & 0 deletions packages/atom.io/internal/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export * from "./molecule"
export * from "./mutable"
export * from "./not-found-error"
export * from "./operation"
export * from "./parse-state-overloads"
export * from "./pretty-print"
export * from "./selector"
export * from "./set-state"
Expand Down
Loading
Loading