From 2a3138d8fec896801e0c0706b96e63b2565c4361 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Tue, 12 May 2026 11:31:19 +0000 Subject: [PATCH] Changes Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com> --- src/devkit/SmokeTest.tsx | 190 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 src/devkit/SmokeTest.tsx diff --git a/src/devkit/SmokeTest.tsx b/src/devkit/SmokeTest.tsx new file mode 100644 index 0000000..f74467a --- /dev/null +++ b/src/devkit/SmokeTest.tsx @@ -0,0 +1,190 @@ +/** + * DEVKIT — `/devkit/smoke` smoke test runner. + * + * Scripted suite that exercises the store + demo pipeline without a headset. + * Run before sending a build to the Quest tester. All steps must be ✅. + */ +import { useState } from 'react'; +import { Link, Navigate } from 'react-router-dom'; +import { useModelStore } from '@/stores/useModelStore'; +import { generateDemoBeamGLB } from '@/lib/generateDemoBeam'; +import { useDevKit } from './useDevKit'; +import { fakeInput } from './fakeInputStore'; +import { startRecording, stopRecording, macroState } from './macroStore'; + +interface StepResult { name: string; ok: boolean; detail: string; ms: number; } + +type Step = { name: string; run: () => Promise }; + +function buildSteps(): Step[] { + const store = useModelStore.getState; + return [ + { + name: 'gerar demo beam (IPE 200)', + run: async () => { + const { blob, fileName, fileSize } = await generateDemoBeamGLB(); + const url = URL.createObjectURL(blob); + store().setModel({ url, fileName, fileSize }); + return `${fileName} • ${(fileSize / 1024).toFixed(1)} KB`; + }, + }, + { + name: 'modelo no store', + run: async () => { + const m = store().model; + if (!m?.url) throw new Error('model.url ausente'); + return m.fileName; + }, + }, + { + name: 'fineTuning reset', + run: async () => { + store().resetFineTuning(); + const ft = store().fineTuning; + if (ft.posX !== 0 || ft.scale !== 1) throw new Error('reset falhou'); + return 'ok'; + }, + }, + { + name: 'medição cria distância correta', + run: async () => { + store().clearMeasurements(); + store().addMeasurePoint({ x: 0, y: 0, z: 0 }); + store().addMeasurePoint({ x: 0.1, y: 0, z: 0 }); + const ms = store().measurements; + if (ms.length !== 1) throw new Error(`esperado 1 medição, obteve ${ms.length}`); + const d = ms[0].distanceMM; + if (Math.abs(d - 100) > 0.01) throw new Error(`esperado 100mm, obteve ${d.toFixed(2)}`); + return `${d.toFixed(2)} mm`; + }, + }, + { + name: 'checklist defaults', + run: async () => { + store().resetChecklist(); + const cl = store().checklist; + if (cl.length !== 4) throw new Error(`esperado 4 itens, obteve ${cl.length}`); + if (!cl.every(i => i.status === 'pending')) throw new Error('itens não-pending'); + return `${cl.length} itens`; + }, + }, + { + name: 'fake input grip simulado', + run: async () => { + fakeInput.left.gripValue = 1; + await new Promise(r => setTimeout(r, 50)); + if (fakeInput.left.gripValue !== 1) throw new Error('grip não foi setado'); + fakeInput.left.gripValue = 0; + return 'ok'; + }, + }, + { + name: 'macro recorder ciclo curto', + run: async () => { + startRecording(); + await new Promise(r => setTimeout(r, 200)); + const m = stopRecording(); + if (!m) throw new Error('macro vazia'); + if (macroState.phase !== 'idle') throw new Error('fase não voltou a idle'); + return `${m.frames.length} frames • ${m.duration.toFixed(0)}ms`; + }, + }, + { + name: 'navigator.xr disponível', + run: async () => { + if (!('xr' in navigator)) throw new Error('navigator.xr ausente'); + return 'ok'; + }, + }, + { + name: 'HTTPS ativo', + run: async () => { + if (location.protocol !== 'https:' && location.hostname !== 'localhost') { + throw new Error(`protocolo ${location.protocol}`); + } + return location.protocol; + }, + }, + ]; +} + +export default function SmokeTest() { + const devkit = useDevKit(); + const [results, setResults] = useState([]); + const [running, setRunning] = useState(false); + + if (!devkit) return ; + + const run = async () => { + setRunning(true); + setResults([]); + const steps = buildSteps(); + const out: StepResult[] = []; + for (const s of steps) { + const t0 = performance.now(); + try { + const detail = await s.run(); + out.push({ name: s.name, ok: true, detail, ms: performance.now() - t0 }); + } catch (e) { + out.push({ name: s.name, ok: false, detail: (e as Error).message, ms: performance.now() - t0 }); + } + setResults([...out]); + } + setRunning(false); + }; + + const passed = results.filter(r => r.ok).length; + const failed = results.filter(r => !r.ok).length; + + return ( +
+
+
+

🧪 DevKit · Smoke Test

+ ← Home +
+

+ Roteiro automático que exercita store, demo, medição, checklist, fake input e gravador. + Execute antes de enviar build para o testador com Quest. +

+
+ + {results.length > 0 && ( + + ✓ {passed} + {' · '} + ✕ {failed} + + )} +
+ +
+ {results.length === 0 && ( +
aguardando execução…
+ )} + {results.map((r, i) => ( +
+
+ {r.ok ? '✓' : '✕'} + {r.name} +
{r.detail}
+
+ {r.ms.toFixed(0)}ms +
+ ))} +
+
+
+ ); +}