fix step tree and step update
continuous-integration/drone/push Build is passing Details

pull/114/head
maxime 1 year ago
parent fcd0a94535
commit 4fe1ddfbd2

@ -0,0 +1,2 @@
VITE_API_ENDPOINT=https://iqball.maxou.dev/api/dotnet-master
#VITE_API_ENDPOINT=http://localhost:5254

@ -7,8 +7,6 @@
"@testing-library/jest-dom": "^5.17.0", "@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2",
"@types/node": "^16.18.59",
"@types/react": "^18.2.31", "@types/react": "^18.2.31",
"@types/react-dom": "^18.2.14", "@types/react-dom": "^18.2.14",
"eslint-plugin-react-refresh": "^0.4.5", "eslint-plugin-react-refresh": "^0.4.5",
@ -23,7 +21,7 @@
"scripts": { "scripts": {
"start": "vite --host", "start": "vite --host",
"build": "vite build", "build": "vite build",
"test": "vite test", "test": "vitest",
"format": "prettier --config .prettierrc '.' --write", "format": "prettier --config .prettierrc '.' --write",
"tsc": "tsc" "tsc": "tsc"
}, },
@ -34,8 +32,10 @@
"eslint": "^8.53.0", "eslint": "^8.53.0",
"eslint-plugin-react": "^7.33.2", "eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
"jsdom": "^24.0.0",
"prettier": "^3.1.0", "prettier": "^3.1.0",
"typescript": "^5.2.2", "typescript": "^5.2.2",
"vite-plugin-svgr": "^4.1.0" "vite-plugin-svgr": "^4.1.0",
"vitest": "^1.3.1"
} }
} }

@ -61,7 +61,8 @@ async function handleResponse(
const expirationDate = Date.parse( const expirationDate = Date.parse(
response.headers.get("Next-Authorization-Expiration-Date")!, response.headers.get("Next-Authorization-Expiration-Date")!,
) )
saveSession({ ...session, auth: { token: nextToken, expirationDate } }) if (nextToken && expirationDate)
saveSession({ ...session, auth: { token: nextToken, expirationDate } })
return response return response
} }

@ -4,7 +4,7 @@ import Draggable from "react-draggable"
export interface RackProps<E extends { key: string | number }> { export interface RackProps<E extends { key: string | number }> {
id: string id: string
objects: E[] objects: E[]
onChange: (objects: E[]) => void onChange?: (objects: E[]) => void
canDetach: (ref: HTMLDivElement) => boolean canDetach: (ref: HTMLDivElement) => boolean
onElementDetached: (ref: HTMLDivElement, el: E) => void onElementDetached: (ref: HTMLDivElement, el: E) => void
render: (e: E) => ReactElement render: (e: E) => ReactElement
@ -20,13 +20,13 @@ interface RackItemProps<E extends { key: string | number }> {
* A container of draggable objects * A container of draggable objects
* */ * */
export function Rack<E extends { key: string | number }>({ export function Rack<E extends { key: string | number }>({
id, id,
objects, objects,
onChange, onChange,
canDetach, canDetach,
onElementDetached, onElementDetached,
render, render,
}: RackProps<E>) { }: RackProps<E>) {
return ( return (
<div <div
id={id} id={id}
@ -44,7 +44,8 @@ export function Rack<E extends { key: string | number }>({
const index = objects.findIndex( const index = objects.findIndex(
(o) => o.key === element.key, (o) => o.key === element.key,
) )
onChange(objects.toSpliced(index, 1)) if (onChange)
onChange(objects.toSpliced(index, 1))
onElementDetached(ref, element) onElementDetached(ref, element)
}} }}
@ -55,10 +56,10 @@ export function Rack<E extends { key: string | number }>({
} }
function RackItem<E extends { key: string | number }>({ function RackItem<E extends { key: string | number }>({
item, item,
onTryDetach, onTryDetach,
render, render,
}: RackItemProps<E>) { }: RackItemProps<E>) {
const divRef = useRef<HTMLDivElement>(null) const divRef = useRef<HTMLDivElement>(null)
return ( return (

@ -4,6 +4,7 @@ import BendableArrow from "../arrows/BendableArrow"
import { useRef } from "react" import { useRef } from "react"
import AddSvg from "../../assets/icon/add.svg?react" import AddSvg from "../../assets/icon/add.svg?react"
import RemoveSvg from "../../assets/icon/remove.svg?react" import RemoveSvg from "../../assets/icon/remove.svg?react"
import { getStepName } from "../../editor/StepsDomain.ts"
export interface StepsTreeProps { export interface StepsTreeProps {
root: StepInfoNode root: StepInfoNode
@ -24,7 +25,7 @@ export default function StepsTree({
<div className="steps-tree"> <div className="steps-tree">
<StepsTreeNode <StepsTreeNode
node={root} node={root}
isNodeRoot={true} rootNode={root}
selectedStepId={selectedStepId} selectedStepId={selectedStepId}
onAddChildren={onAddChildren} onAddChildren={onAddChildren}
onRemoveNode={onRemoveNode} onRemoveNode={onRemoveNode}
@ -36,7 +37,8 @@ export default function StepsTree({
interface StepsTreeContentProps { interface StepsTreeContentProps {
node: StepInfoNode node: StepInfoNode
isNodeRoot: boolean rootNode: StepInfoNode
selectedStepId: number selectedStepId: number
onAddChildren: (parent: StepInfoNode) => void onAddChildren: (parent: StepInfoNode) => void
onRemoveNode: (node: StepInfoNode) => void onRemoveNode: (node: StepInfoNode) => void
@ -45,7 +47,7 @@ interface StepsTreeContentProps {
function StepsTreeNode({ function StepsTreeNode({
node, node,
isNodeRoot, rootNode,
selectedStepId, selectedStepId,
onAddChildren, onAddChildren,
onRemoveNode, onRemoveNode,
@ -53,14 +55,15 @@ function StepsTreeNode({
}: StepsTreeContentProps) { }: StepsTreeContentProps) {
const ref = useRef<HTMLDivElement>(null) const ref = useRef<HTMLDivElement>(null)
const stepId = getStepName(rootNode, node.id)
return ( return (
<div ref={ref} className={"step-group"}> <div ref={ref} className={"step-group"}>
{node.children.map((child) => ( {node.children.map((child) => (
<BendableArrow <BendableArrow
key={child.id} key={child.id}
area={ref} area={ref}
startPos={"step-piece-" + node.id} startPos={"step-piece-" + stepId}
segments={[{ next: "step-piece-" + child.id }]} segments={[{ next: "step-piece-" + getStepName(rootNode, child.id)}]}
onSegmentsChanges={() => {}} onSegmentsChanges={() => {}}
forceStraight={true} forceStraight={true}
wavy={false} wavy={false}
@ -70,11 +73,11 @@ function StepsTreeNode({
/> />
))} ))}
<StepPiece <StepPiece
id={node.id} name={stepId}
isSelected={selectedStepId === node.id} isSelected={selectedStepId === node.id}
onAddButtonClicked={() => onAddChildren(node)} onAddButtonClicked={() => onAddChildren(node)}
onRemoveButtonClicked={ onRemoveButtonClicked={
isNodeRoot ? undefined : () => onRemoveNode(node) rootNode.id === node.id ? undefined : () => onRemoveNode(node)
} }
onSelected={() => onStepSelected(node)} onSelected={() => onStepSelected(node)}
/> />
@ -82,7 +85,7 @@ function StepsTreeNode({
{node.children.map((child) => ( {node.children.map((child) => (
<StepsTreeNode <StepsTreeNode
key={child.id} key={child.id}
isNodeRoot={false} rootNode={rootNode}
selectedStepId={selectedStepId} selectedStepId={selectedStepId}
node={child} node={child}
onAddChildren={onAddChildren} onAddChildren={onAddChildren}
@ -96,7 +99,7 @@ function StepsTreeNode({
} }
interface StepPieceProps { interface StepPieceProps {
id: number name: string
isSelected: boolean isSelected: boolean
onAddButtonClicked?: () => void onAddButtonClicked?: () => void
onRemoveButtonClicked?: () => void onRemoveButtonClicked?: () => void
@ -104,7 +107,7 @@ interface StepPieceProps {
} }
function StepPiece({ function StepPiece({
id, name,
isSelected, isSelected,
onAddButtonClicked, onAddButtonClicked,
onRemoveButtonClicked, onRemoveButtonClicked,
@ -112,7 +115,7 @@ function StepPiece({
}: StepPieceProps) { }: StepPieceProps) {
return ( return (
<div <div
id={"step-piece-" + id} id={"step-piece-" + name}
tabIndex={1} tabIndex={1}
className={ className={
"step-piece " + (isSelected ? "step-piece-selected" : "") "step-piece " + (isSelected ? "step-piece-selected" : "")
@ -132,7 +135,7 @@ function StepPiece({
/> />
)} )}
</div> </div>
<p>{id}</p> <p>{name}</p>
</div> </div>
) )
} }

@ -18,6 +18,23 @@ export function addStepNode(
} }
} }
export function getStepName(root: StepInfoNode, step: number): string {
let ord = 1
const nodes = [root]
while (nodes.length > 0) {
const node = nodes.pop()!
if (node.id === step)
break
ord++
nodes.push(...[...node.children].reverse())
}
return ord.toString()
}
export function getStepNode( export function getStepNode(
root: StepInfoNode, root: StepInfoNode,
stepId: number, stepId: number,

@ -200,9 +200,9 @@ export function moveComponent(
phantomIdx == 0 phantomIdx == 0
? origin ? origin
: getComponent( : getComponent(
originPathItems[phantomIdx - 1], originPathItems[phantomIdx - 1],
content.components, content.components,
) )
// detach the action from the screen target and transform it to a regular move action to the phantom. // detach the action from the screen target and transform it to a regular move action to the phantom.
content = updateComponent( content = updateComponent(
{ {
@ -210,18 +210,18 @@ export function moveComponent(
actions: playerBeforePhantom.actions.map((a) => actions: playerBeforePhantom.actions.map((a) =>
a.target === referent a.target === referent
? { ? {
...a, ...a,
segments: a.segments.toSpliced( segments: a.segments.toSpliced(
a.segments.length - 2, a.segments.length - 2,
1, 1,
{ {
...a.segments[a.segments.length - 1], ...a.segments[a.segments.length - 1],
next: component.id, next: component.id,
}, },
), ),
target: component.id, target: component.id,
type: ActionKind.MOVE, type: ActionKind.MOVE,
} }
: a, : a,
), ),
}, },
@ -234,9 +234,9 @@ export function moveComponent(
...component, ...component,
pos: isPhantom pos: isPhantom
? { ? {
type: "fixed", type: "fixed",
...newPos, ...newPos,
} }
: newPos, : newPos,
}, },
content, content,
@ -315,15 +315,15 @@ export function computeTerminalState(
content.components.filter((c) => c.type !== "phantom") as ( content.components.filter((c) => c.type !== "phantom") as (
| Player | Player
| CourtObject | CourtObject
)[] )[]
const componentsTargetedState = nonPhantomComponents.map((comp) => const componentsTargetedState = nonPhantomComponents.map((comp) =>
comp.type === "player" comp.type === "player"
? getPlayerTerminalState(comp, content, computedPositions) ? getPlayerTerminalState(comp, content, computedPositions)
: { : {
...comp, ...comp,
frozen: true, frozen: true,
}, },
) )
return { return {
@ -399,20 +399,12 @@ export function drainTerminalStateOnChildContent(
parentTerminalState: StepContent, parentTerminalState: StepContent,
childContent: StepContent, childContent: StepContent,
): StepContent | null { ): StepContent | null {
let gotUpdated = false
//filter out all frozen components that are not present on the parent's terminal state anymore
childContent = { let gotUpdated = false
components: childContent.components.filter(
(comp) =>
comp.type === "phantom" ||
(comp.frozen &&
tryGetComponent(comp.id, parentTerminalState.components)),
),
}
for (const parentComponent of parentTerminalState.components) { for (const parentComponent of parentTerminalState.components) {
const childComponent = tryGetComponent( let childComponent = tryGetComponent(
parentComponent.id, parentComponent.id,
childContent.components, childContent.components,
) )
@ -443,13 +435,16 @@ export function drainTerminalStateOnChildContent(
if (newContentResult) { if (newContentResult) {
gotUpdated = true gotUpdated = true
childContent = newContentResult childContent = newContentResult
childComponent = getComponent<Player>(childComponent.id, newContentResult?.components)
} }
// also update the position of the player if it has been moved // update the position of the player if it has been moved
if (!equals(childComponent.pos, parentComponent.pos)) { // also force update if the child component is not frozen (the component was introduced previously by the child step but the parent added it afterward)
if (!childComponent.frozen || !equals(childComponent.pos, parentComponent.pos)) {
gotUpdated = true gotUpdated = true
childContent = updateComponent( childContent = updateComponent(
{ {
...childComponent, ...childComponent,
frozen: true,
pos: parentComponent.pos, pos: parentComponent.pos,
}, },
childContent, childContent,
@ -457,5 +452,19 @@ export function drainTerminalStateOnChildContent(
} }
} }
const initialChildCompsCount = childContent.components.length
//filter out all frozen components that are not present on the parent's terminal state anymore
childContent = {
components: childContent.components.filter(
(comp) =>
comp.type === "phantom" ||
!comp.frozen ||
tryGetComponent(comp.id, parentTerminalState.components)
),
}
gotUpdated ||= childContent.components.length !== initialChildCompsCount
return gotUpdated ? childContent : null return gotUpdated ? childContent : null
} }

@ -102,7 +102,7 @@ const GUEST_MODE_STEP_ROOT_NODE_INFO_STORAGE_KEY = "guest_mode_step_tree"
const GUEST_MODE_TITLE_STORAGE_KEY = "guest_mode_title" const GUEST_MODE_TITLE_STORAGE_KEY = "guest_mode_title"
// The step identifier the editor will always open on // The step identifier the editor will always open on
const ROOT_STEP_ID = 1 const GUEST_MODE_ROOT_STEP_ID = 1
type ComputedRelativePositions = Map<ComponentId, Pos> type ComputedRelativePositions = Map<ComponentId, Pos>
@ -131,7 +131,7 @@ function EditorPortal({ guestMode }: EditorPageProps) {
function GuestModeEditor() { function GuestModeEditor() {
const storageContent = localStorage.getItem( const storageContent = localStorage.getItem(
GUEST_MODE_STEP_CONTENT_STORAGE_KEY + ROOT_STEP_ID, GUEST_MODE_STEP_CONTENT_STORAGE_KEY + GUEST_MODE_ROOT_STEP_ID,
) )
const stepInitialContent: StepContent = { const stepInitialContent: StepContent = {
@ -148,10 +148,10 @@ function GuestModeEditor() {
if (storageContent == null) { if (storageContent == null) {
localStorage.setItem( localStorage.setItem(
GUEST_MODE_STEP_ROOT_NODE_INFO_STORAGE_KEY, GUEST_MODE_STEP_ROOT_NODE_INFO_STORAGE_KEY,
JSON.stringify({ id: ROOT_STEP_ID, children: [] }), JSON.stringify({ id: GUEST_MODE_ROOT_STEP_ID, children: [] }),
) )
localStorage.setItem( localStorage.setItem(
GUEST_MODE_STEP_CONTENT_STORAGE_KEY + ROOT_STEP_ID, GUEST_MODE_STEP_CONTENT_STORAGE_KEY + GUEST_MODE_ROOT_STEP_ID,
JSON.stringify(stepInitialContent), JSON.stringify(stepInitialContent),
) )
} }
@ -161,7 +161,7 @@ function GuestModeEditor() {
"Nouvelle Tactique" "Nouvelle Tactique"
const courtRef = useRef<HTMLDivElement>(null) const courtRef = useRef<HTMLDivElement>(null)
const [stepId, setStepId] = useState(ROOT_STEP_ID) const [stepId, setStepId] = useState(GUEST_MODE_ROOT_STEP_ID)
const [stepContent, setStepContent, saveState] = useContentState( const [stepContent, setStepContent, saveState] = useContentState(
stepInitialContent, stepInitialContent,
SaveStates.Guest, SaveStates.Guest,
@ -281,7 +281,7 @@ function GuestModeEditor() {
function UserModeEditor() { function UserModeEditor() {
const [tactic, setTactic] = useState<TacticDto | null>(null) const [tactic, setTactic] = useState<TacticDto | null>(null)
const [stepsTree, setStepsTree] = useState<StepInfoNode>({ const [stepsTree, setStepsTree] = useState<StepInfoNode>({
id: ROOT_STEP_ID, id: -1,
children: [], children: [],
}) })
const { tacticId: idStr } = useParams() const { tacticId: idStr } = useParams()
@ -289,7 +289,7 @@ function UserModeEditor() {
const navigation = useNavigate() const navigation = useNavigate()
const courtRef = useRef<HTMLDivElement>(null) const courtRef = useRef<HTMLDivElement>(null)
const [stepId, setStepId] = useState(1) const [stepId, setStepId] = useState(-1)
const saveContent = useCallback( const saveContent = useCallback(
async (content: StepContent) => { async (content: StepContent) => {
@ -353,29 +353,38 @@ function UserModeEditor() {
async function initialize() { async function initialize() {
const infoResponsePromise = fetchAPIGet(`tactics/${tacticId}`) const infoResponsePromise = fetchAPIGet(`tactics/${tacticId}`)
const treeResponsePromise = fetchAPIGet(`tactics/${tacticId}/tree`) const treeResponsePromise = fetchAPIGet(`tactics/${tacticId}/tree`)
const contentResponsePromise = fetchAPIGet(
`tactics/${tacticId}/steps/${ROOT_STEP_ID}`,
)
const infoResponse = await infoResponsePromise const infoResponse = await infoResponsePromise
const treeResponse = await treeResponsePromise const treeResponse = await treeResponsePromise
const contentResponse = await contentResponsePromise
const { name, courtType } = await infoResponse.json()
const { root } = await treeResponse.json()
if ( if (
infoResponse.status == 401 || infoResponse.status == 401 ||
treeResponse.status == 401 || treeResponse.status == 401
contentResponse.status == 401
) { ) {
navigation("/login") navigation("/login")
return return
} }
const { name, courtType } = await infoResponse.json() const contentResponsePromise = fetchAPIGet(
`tactics/${tacticId}/steps/${root.id}`,
)
const contentResponse = await contentResponsePromise
if (contentResponse.status == 401) {
navigation("/login")
return
}
const content = await contentResponse.json() const content = await contentResponse.json()
const { root } = await treeResponse.json()
setTactic({ id: tacticId, name, courtType }) setTactic({ id: tacticId, name, courtType })
setStepsTree(root) setStepsTree(root)
setStepId(root.id)
setStepContent(content, false) setStepContent(content, false)
} }
@ -492,12 +501,8 @@ function EditorPage({
const [rootStepsNode, setRootStepsNode] = useState(initialStepsNode) const [rootStepsNode, setRootStepsNode] = useState(initialStepsNode)
const [allies, setAllies] = useState(() => const allies = getRackPlayers(PlayerTeam.Allies, content.components)
getRackPlayers(PlayerTeam.Allies, content.components), const opponents = getRackPlayers(PlayerTeam.Opponents, content.components)
)
const [opponents, setOpponents] = useState(() =>
getRackPlayers(PlayerTeam.Opponents, content.components),
)
const [objects, setObjects] = useState<RackedCourtObject[]>(() => const [objects, setObjects] = useState<RackedCourtObject[]>(() =>
isBallOnCourt(content) ? [] : [{ key: "ball" }], isBallOnCourt(content) ? [] : [{ key: "ball" }],
@ -521,25 +526,6 @@ function EditorPage({
: new Map() : new Map()
}, [content, courtRef]) }, [content, courtRef])
// const setContent = useCallback(
// (newState: SetStateAction<StepContent>) => {
// setCurrentStepContent((c) => {
// const state =
// typeof newState === "function"
// ? newState(c.content)
// : newState
//
// const courtBounds = courtRef.current?.getBoundingClientRect()
// const relativePositions: ComputedRelativePositions = courtBounds
// ? computeRelativePositions(courtBounds, state)
// : new Map()
//
// return state
// })
// },
// [setCurrentStepContent],
// )
const setComponents = (action: SetStateAction<TacticComponent[]>) => { const setComponents = (action: SetStateAction<TacticComponent[]>) => {
setContent((c) => ({ setContent((c) => ({
...c, ...c,
@ -553,25 +539,9 @@ function EditorPage({
}, [setObjects, content]) }, [setObjects, content])
const insertRackedPlayer = (player: Player) => { const insertRackedPlayer = (player: Player) => {
let setter
switch (player.team) {
case PlayerTeam.Opponents:
setter = setOpponents
break
case PlayerTeam.Allies:
setter = setAllies
}
if (player.ballState == BallState.HOLDS_BY_PASS) { if (player.ballState == BallState.HOLDS_BY_PASS) {
setObjects([{ key: "ball" }]) setObjects([{ key: "ball" }])
} }
setter((players) => [
...players,
{
team: player.team,
pos: player.role,
key: player.role,
},
])
} }
const doRemovePlayer = useCallback( const doRemovePlayer = useCallback(
@ -676,7 +646,7 @@ function EditorPage({
), ),
] ]
}, },
[content, doMoveBall, previewAction?.isInvalid, setContent], [content, courtRef, doMoveBall, previewAction?.isInvalid, setContent],
) )
const renderPlayer = useCallback( const renderPlayer = useCallback(
@ -730,14 +700,7 @@ function EditorPage({
/> />
) )
}, },
[ [courtRef, content, relativePositions, courtBounds, renderAvailablePlayerActions, validatePlayerPosition, doRemovePlayer],
content,
relativePositions,
courtBounds,
validatePlayerPosition,
doRemovePlayer,
renderAvailablePlayerActions,
],
) )
const doDeleteAction = useCallback( const doDeleteAction = useCallback(
@ -811,7 +774,7 @@ function EditorPage({
/> />
) )
}), }),
[doDeleteAction, doUpdateAction], [courtRef, doDeleteAction, doUpdateAction],
) )
return ( return (
@ -836,7 +799,7 @@ function EditorPage({
</div> </div>
<div id="topbar-right"> <div id="topbar-right">
<button onClick={() => setStepsTreeVisible((b) => !b)}> <button onClick={() => setStepsTreeVisible((b) => !b)}>
STEPS ETAPES
</button> </button>
</div> </div>
</div> </div>
@ -846,7 +809,6 @@ function EditorPage({
<PlayerRack <PlayerRack
id={"allies"} id={"allies"}
objects={allies} objects={allies}
setObjects={setAllies}
setComponents={setComponents} setComponents={setComponents}
courtRef={courtRef} courtRef={courtRef}
/> />
@ -881,7 +843,6 @@ function EditorPage({
<PlayerRack <PlayerRack
id={"opponents"} id={"opponents"}
objects={opponents} objects={opponents}
setObjects={setOpponents}
setComponents={setComponents} setComponents={setComponents}
courtRef={courtRef} courtRef={courtRef}
/> />
@ -983,7 +944,7 @@ function EditorStepsTree({
interface PlayerRackProps { interface PlayerRackProps {
id: string id: string
objects: RackedPlayer[] objects: RackedPlayer[]
setObjects: (state: RackedPlayer[]) => void setObjects?: (state: RackedPlayer[]) => void
setComponents: ( setComponents: (
f: (components: TacticComponent[]) => TacticComponent[], f: (components: TacticComponent[]) => TacticComponent[],
) => void ) => void

@ -0,0 +1,40 @@
import { beforeAll, expect, test } from "vitest"
import { fetchAPI } from "../../src/Fetcher"
import { saveSession } from "../../src/api/session"
async function login() {
const response = await fetchAPI("auth/token/", { email: "maxime@mail.com", password: "123456" })
expect(response.status).toBe(200)
const { token, expirationDate } = await response.json()
saveSession({ auth: { token, expirationDate: Date.parse(expirationDate) } })
}
beforeAll(login)
test("create tactic", async () => {
await login()
const response = await fetchAPI("tactics", { courtType: "PLAIN", name: "test tactic" })
expect(response.status).toBe(200)
})
test("spam step creation test", async () => {
const createTacticResponse = await fetchAPI("tactics", { courtType: "PLAIN", name: "test tactic" })
expect(createTacticResponse.status).toBe(200)
const { id } = await createTacticResponse.json()
const tasks = Array.from({length: 200})
.map(async () => {
const response = await fetchAPI(`tactics/${id}/steps`, { parentId: 1, content: { components: [] } })
expect(response.status).toBe(200)
const { stepId } = await response.json()
return stepId
})
const steps = []
for (const task of tasks) {
steps.push(await task)
}
steps.sort((a, b) => a - b)
const expected = Array.from({length: 200}, (_, i) => i + 2)
expect(steps).toEqual(expected)
})

@ -8,6 +8,9 @@ export default defineConfig({
build: { build: {
target: "es2021", target: "es2021",
}, },
test: {
environment: "jsdom"
},
plugins: [ plugins: [
react(), react(),
cssInjectedByJsPlugin({ cssInjectedByJsPlugin({

Loading…
Cancel
Save