test(velxio): add comprehensive unit test suites for velxio-manager, hooks redirect, and backend markdown parsing
This commit is contained in:
parent
ea167b45a7
commit
812ede0acc
|
|
@ -0,0 +1,56 @@
|
|||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { handle } from './hooks.server';
|
||||
|
||||
describe('hooks.server handle', () => {
|
||||
it('should redirect /editor to /velxio/editor with 302', async () => {
|
||||
const mockEvent: any = {
|
||||
request: {
|
||||
method: 'GET',
|
||||
headers: new Headers()
|
||||
},
|
||||
url: new URL('https://sinau-c-dev.manakin-gentoo.ts.net/editor')
|
||||
};
|
||||
|
||||
const resolve = vi.fn();
|
||||
const response = await handle({ event: mockEvent, resolve });
|
||||
|
||||
expect(response.status).toBe(302);
|
||||
expect(response.headers.get('location')).toBe('https://sinau-c-dev.manakin-gentoo.ts.net/velxio/editor');
|
||||
expect(resolve).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should redirect /editor/ with query parameters to /velxio/editor?foo=bar with 302', async () => {
|
||||
const mockEvent: any = {
|
||||
request: {
|
||||
method: 'GET',
|
||||
headers: new Headers()
|
||||
},
|
||||
url: new URL('https://sinau-c-dev.manakin-gentoo.ts.net/editor/?embed=true&desktopLayout=true')
|
||||
};
|
||||
|
||||
const resolve = vi.fn();
|
||||
const response = await handle({ event: mockEvent, resolve });
|
||||
|
||||
expect(response.status).toBe(302);
|
||||
expect(response.headers.get('location')).toBe('https://sinau-c-dev.manakin-gentoo.ts.net/velxio/editor?embed=true&desktopLayout=true');
|
||||
expect(resolve).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should pass through normal page routes to resolve()', async () => {
|
||||
const mockEvent: any = {
|
||||
request: {
|
||||
method: 'GET',
|
||||
headers: new Headers()
|
||||
},
|
||||
url: new URL('https://sinau-c-dev.manakin-gentoo.ts.net/teacher/content')
|
||||
};
|
||||
|
||||
const mockResponse = new Response('ok');
|
||||
const resolve = vi.fn().mockResolvedValue(mockResponse);
|
||||
|
||||
const response = await handle({ event: mockEvent, resolve });
|
||||
|
||||
expect(resolve).toHaveBeenCalledWith(mockEvent);
|
||||
expect(response).toBe(mockResponse);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
import { getVelxioState } from './velxio-manager';
|
||||
|
||||
describe('velxio-manager', () => {
|
||||
describe('getVelxioState', () => {
|
||||
it('should return null when iframe is null or contentWindow is missing', () => {
|
||||
expect(getVelxioState(null)).toBeNull();
|
||||
|
||||
const mockIframeWithoutWin = {} as HTMLIFrameElement;
|
||||
expect(getVelxioState(mockIframeWithoutWin)).toBeNull();
|
||||
});
|
||||
|
||||
it('should return null when Velxio Zustand stores are not attached to window', () => {
|
||||
const mockIframe = {
|
||||
contentWindow: {}
|
||||
} as HTMLIFrameElement;
|
||||
|
||||
expect(getVelxioState(mockIframe)).toBeNull();
|
||||
});
|
||||
|
||||
it('should extract code and circuit structure from Velxio stores correctly', () => {
|
||||
const mockEditorStore = {
|
||||
fileGroups: {
|
||||
'grp-1': [
|
||||
{ name: 'header.h', content: '#define LED 13' },
|
||||
{ name: 'sketch.ino', content: 'void setup() { pinMode(LED, OUTPUT); }\nvoid loop() {}' }
|
||||
]
|
||||
},
|
||||
files: []
|
||||
};
|
||||
|
||||
const mockSimStore = {
|
||||
activeBoardId: 'board-1',
|
||||
boards: [
|
||||
{
|
||||
id: 'board-1',
|
||||
boardKind: 'arduino-uno',
|
||||
activeFileGroupId: 'grp-1'
|
||||
}
|
||||
],
|
||||
components: [
|
||||
{
|
||||
id: 'uno-1',
|
||||
metadataId: 'arduino-uno',
|
||||
x: 100.45,
|
||||
y: 200.89,
|
||||
properties: { rotation: 90, color: 'blue' }
|
||||
},
|
||||
{
|
||||
id: 'led-1',
|
||||
metadataId: 'led',
|
||||
x: 50,
|
||||
y: 80,
|
||||
properties: { color: 'red' }
|
||||
}
|
||||
],
|
||||
wires: [
|
||||
{
|
||||
start: { componentId: 'uno-1', pinName: '13' },
|
||||
end: { componentId: 'led-1', pinName: 'A' }
|
||||
},
|
||||
null
|
||||
]
|
||||
};
|
||||
|
||||
const mockIframe = {
|
||||
contentWindow: {
|
||||
__VELXIO_EDITOR_STORE__: { getState: () => mockEditorStore },
|
||||
__VELXIO_SIMULATOR_STORE__: { getState: () => mockSimStore }
|
||||
}
|
||||
} as unknown as HTMLIFrameElement;
|
||||
|
||||
const result = getVelxioState(mockIframe);
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.code).toBe('void setup() { pinMode(LED, OUTPUT); }\nvoid loop() {}');
|
||||
|
||||
const parsedCircuit = JSON.parse(result!.circuit);
|
||||
expect(parsedCircuit.board).toBe('arduino:avr:uno');
|
||||
expect(parsedCircuit.components).toHaveLength(2);
|
||||
expect(parsedCircuit.components[0]).toEqual({
|
||||
id: 'uno-1',
|
||||
type: 'arduino-uno',
|
||||
x: 100.5,
|
||||
y: 200.9,
|
||||
rotation: 90,
|
||||
props: { rotation: 90, color: 'blue' }
|
||||
});
|
||||
expect(parsedCircuit.components[1]).toEqual({
|
||||
id: 'led-1',
|
||||
type: 'led',
|
||||
x: 50,
|
||||
y: 80,
|
||||
rotation: 0,
|
||||
props: { color: 'red' }
|
||||
});
|
||||
expect(parsedCircuit.wires).toHaveLength(1);
|
||||
expect(parsedCircuit.wires[0]).toEqual({
|
||||
start: { componentId: 'uno-1', pinName: '13' },
|
||||
end: { componentId: 'led-1', pinName: 'A' }
|
||||
});
|
||||
});
|
||||
|
||||
it('should fallback to default files list and handle custom board FQBN', () => {
|
||||
const mockEditorStore = {
|
||||
fileGroups: {},
|
||||
files: [
|
||||
{ name: 'main.cpp', content: 'int main() { return 0; }' }
|
||||
]
|
||||
};
|
||||
|
||||
const mockSimStore = {
|
||||
activeBoardId: 'custom-board',
|
||||
boards: [
|
||||
{
|
||||
id: 'custom-board',
|
||||
boardKind: 'custom:arch:myboard'
|
||||
}
|
||||
],
|
||||
components: [],
|
||||
wires: []
|
||||
};
|
||||
|
||||
const mockIframe = {
|
||||
contentWindow: {
|
||||
__VELXIO_EDITOR_STORE__: { getState: () => mockEditorStore },
|
||||
__VELXIO_SIMULATOR_STORE__: { getState: () => mockSimStore }
|
||||
}
|
||||
} as unknown as HTMLIFrameElement;
|
||||
|
||||
const result = getVelxioState(mockIframe);
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.code).toBe('int main() { return 0; }');
|
||||
|
||||
const parsedCircuit = JSON.parse(result!.circuit);
|
||||
expect(parsedCircuit.board).toBe('custom:arch:myboard');
|
||||
expect(parsedCircuit.components).toEqual([]);
|
||||
expect(parsedCircuit.wires).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { VelxioBridge } from '$services/velxio-bridge';
|
||||
import type { LessonContent } from '$types/lesson';
|
||||
import { VelxioBridge } from './velxio-bridge';
|
||||
import type { LessonContent } from '../types/lesson';
|
||||
|
||||
const BOARD_KIND_MAP: Record<string, string> = {
|
||||
'arduino-uno': 'arduino:avr:uno',
|
||||
|
|
|
|||
|
|
@ -1,8 +1,20 @@
|
|||
import { defineConfig } from 'vitest/config';
|
||||
import path from 'path';
|
||||
|
||||
export default defineConfig({
|
||||
resolve: {
|
||||
alias: {
|
||||
$components: path.resolve(__dirname, 'src/lib/components'),
|
||||
$stores: path.resolve(__dirname, 'src/lib/stores'),
|
||||
$services: path.resolve(__dirname, 'src/lib/services'),
|
||||
$types: path.resolve(__dirname, 'src/lib/types'),
|
||||
$actions: path.resolve(__dirname, 'src/lib/actions'),
|
||||
$lib: path.resolve(__dirname, 'src/lib')
|
||||
}
|
||||
},
|
||||
test: {
|
||||
environment: 'node',
|
||||
include: ['src/**/*.test.ts']
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
import pytest
|
||||
|
||||
pytestmark = pytest.mark.unit
|
||||
|
||||
from services.lesson_service import _render_markdown_string
|
||||
|
||||
|
||||
def test_render_markdown_velxio_with_code_and_circuit():
|
||||
content = """# LED Blink
|
||||
Berikut adalah materi kendali LED pada Arduino.
|
||||
|
||||
---INITIAL_CODE_ARDUINO---
|
||||
void setup() {
|
||||
pinMode(13, OUTPUT);
|
||||
}
|
||||
void loop() {
|
||||
digitalWrite(13, HIGH);
|
||||
delay(500);
|
||||
digitalWrite(13, LOW);
|
||||
delay(500);
|
||||
}
|
||||
---END_INITIAL_CODE_ARDUINO---
|
||||
|
||||
---VELXIO_CIRCUIT---
|
||||
{
|
||||
"board": "arduino:avr:uno",
|
||||
"components": [
|
||||
{ "id": "uno-1", "type": "arduino-uno", "x": 0, "y": 0 }
|
||||
]
|
||||
}
|
||||
---END_VELXIO_CIRCUIT---
|
||||
"""
|
||||
parsed = _render_markdown_string(content)
|
||||
assert 'velxio' in parsed['active_tabs']
|
||||
assert parsed['initial_code_arduino'].startswith('void setup()')
|
||||
assert 'arduino:avr:uno' in parsed['velxio_circuit']
|
||||
|
||||
|
||||
def test_render_markdown_velxio_circuit_only():
|
||||
content = """# Sirkuit Saja
|
||||
---VELXIO_CIRCUIT---
|
||||
{"board": "arduino:avr:uno"}
|
||||
---END_VELXIO_CIRCUIT---
|
||||
"""
|
||||
parsed = _render_markdown_string(content)
|
||||
assert parsed['active_tabs'] == ['velxio']
|
||||
assert parsed.get('velxio_circuit') == '{"board": "arduino:avr:uno"}'
|
||||
|
||||
|
||||
def test_render_markdown_velxio_exclusive_priority_over_c():
|
||||
content = """# Prioritas Arduino
|
||||
---INITIAL_CODE_ARDUINO---
|
||||
void setup() {}
|
||||
---END_INITIAL_CODE_ARDUINO---
|
||||
|
||||
---INITIAL_CODE---
|
||||
int main() { return 0; }
|
||||
---END_INITIAL_CODE---
|
||||
"""
|
||||
parsed = _render_markdown_string(content)
|
||||
# INITIAL_CODE_ARDUINO harus menjadikan mode velxio eksklusif tanpa tab 'c'
|
||||
assert 'velxio' in parsed['active_tabs']
|
||||
assert 'c' not in parsed['active_tabs']
|
||||
Loading…
Reference in New Issue