diff --git a/scripts/common/functions.sh b/scripts/common/functions.sh index 36e8b76..eab3ef2 100755 --- a/scripts/common/functions.sh +++ b/scripts/common/functions.sh @@ -193,6 +193,81 @@ remove_stamp() { rm -f "$stamp_file" } +# Compute hash of source files in a directory +# Usage: compute_source_hash /path/to/source "*.cpp" "*.h" +compute_source_hash() { + local source_dir="$1" + shift + local patterns=("$@") + + # Build find command for all patterns + local find_args=() + for pattern in "${patterns[@]}"; do + if [ ${#find_args[@]} -gt 0 ]; then + find_args+=("-o") + fi + find_args+=("-name" "$pattern") + done + + # Hash all matching files (sorted for consistency) + # Use md5 on macOS, md5sum on Linux (Docker) + if command -v md5 &>/dev/null; then + find "$source_dir" -type f \( "${find_args[@]}" \) 2>/dev/null | \ + sort | \ + xargs cat 2>/dev/null | \ + md5 + else + find "$source_dir" -type f \( "${find_args[@]}" \) 2>/dev/null | \ + sort | \ + xargs cat 2>/dev/null | \ + md5sum | \ + cut -d' ' -f1 + fi +} + +# Create stamp with source hash +# Usage: create_source_stamp "wxwidgets" /path/to/source "*.cpp" "*.h" +create_source_stamp() { + local name="$1" + local source_dir="$2" + shift 2 + local patterns=("$@") + + local stamp_dir="${BUILD_ROOT:-$PROJECT_ROOT/build-wasm}/stamps" + mkdir -p "$stamp_dir" + local stamp_file="$stamp_dir/$name.stamp" + + local hash=$(compute_source_hash "$source_dir" "${patterns[@]}") + echo "$hash" > "$stamp_file" + log_info "Created stamp: $name (hash: ${hash:0:8}...)" +} + +# Check if source stamp is still valid +# Usage: check_source_stamp "wxwidgets" /path/to/source "*.cpp" "*.h" +# Returns: 0 if valid (no rebuild needed), 1 if invalid (rebuild needed) +check_source_stamp() { + local name="$1" + local source_dir="$2" + shift 2 + local patterns=("$@") + + local stamp_file="${BUILD_ROOT:-$PROJECT_ROOT/build-wasm}/stamps/$name.stamp" + + if [ ! -f "$stamp_file" ]; then + return 1 # No stamp, need build + fi + + local stored_hash=$(cat "$stamp_file") + local current_hash=$(compute_source_hash "$source_dir" "${patterns[@]}") + + if [ "$stored_hash" = "$current_hash" ]; then + return 0 # Up to date + else + log_info "Source changed for $name (${stored_hash:0:8}... -> ${current_hash:0:8}...)" + return 1 # Changed, need rebuild + fi +} + # Build if stamp doesn't exist build_if_needed() { local name="$1" diff --git a/scripts/kicad/build-pcbnew.sh b/scripts/kicad/build-pcbnew.sh index a8cc35b..44ba10c 100755 --- a/scripts/kicad/build-pcbnew.sh +++ b/scripts/kicad/build-pcbnew.sh @@ -12,6 +12,11 @@ # --debug Build with debug symbols (default) # --release Build optimized without debug symbols # -j N Parallel compilation jobs (default: 1) +# +# Stamp System: +# Dependencies use source-hash stamps to detect when rebuilds are needed. +# wxWidgets rebuilds automatically when source files (*.cpp, *.h, *.c) change. +# To force a rebuild, delete the stamp: rm -f build-wasm/stamps/wxwidgets.stamp set -e @@ -101,16 +106,13 @@ if [ $NO_CLEAN -eq 1 ] && check_stamp "${KICAD_STAMP}"; then exit 0 fi -# Step 4: Build wxWidgets if not present -WXWIDGETS_STAMP="${BUILD_ROOT}/stamps/wxwidgets.stamp" -if [ ! -f "${WX_BUILD}/lib/libwx_baseu-3.2.a" ]; then +# Step 4: Build wxWidgets if source changed or not present +WX_SOURCE="${PROJECT_ROOT}/wxwidgets/src" +if [ ! -f "${WX_BUILD}/lib/libwx_baseu-3.2.a" ] || \ + ! check_source_stamp "wxwidgets" "$WX_SOURCE" "*.cpp" "*.h" "*.c"; then log_info "Building wxWidgets..." "${SCRIPT_DIR}/../build-wxuniversal-wasm.sh" --no-clean - # Create stamp after successful build - touch "${WXWIDGETS_STAMP}" -elif [ ! -f "${WXWIDGETS_STAMP}" ]; then - # Library exists but no stamp - create one - touch "${WXWIDGETS_STAMP}" + create_source_stamp "wxwidgets" "$WX_SOURCE" "*.cpp" "*.h" "*.c" fi log_info "Building KiCad PCBnew ${KICAD_VERSION} for WASM..." diff --git a/tests/e2e/maximize.spec.ts b/tests/e2e/maximize.spec.ts new file mode 100644 index 0000000..d3d2179 --- /dev/null +++ b/tests/e2e/maximize.spec.ts @@ -0,0 +1,109 @@ +// Maximize Test - Reproduces KiCad startup issue where Maximize() results in tiny window +// This tests that wxFrame::Maximize() works correctly when called at startup +import { test, expect, tryLoadApp, getCanvasBox } from './utils/fixtures'; + +test.describe('wxFrame::Maximize() Tests', () => { + + test('Maximize test app loads successfully', async ({ page, testLogger }) => { + await page.goto('/standalone/maximize/maximize_test.html'); + const loaded = await tryLoadApp(page); + + await page.screenshot({ path: 'test-results/maximize-01-loaded.png', fullPage: true }); + + const hasStartup = testLogger.consoleLogs.some(l => l.includes('[MAXIMIZE_TEST] Maximize test app started')); + + expect(loaded, 'Maximize app should load').toBe(true); + expect(hasStartup, 'Startup log should be present').toBe(true); + expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0); + }); + + test('Maximized window has reasonable size (not tiny)', async ({ page, testLogger }) => { + await page.goto('/standalone/maximize/maximize_test.html'); + const loaded = await tryLoadApp(page); + if (!loaded) { + test.skip(); + return; + } + + // Wait for maximize to complete + await page.waitForTimeout(500); + + await page.screenshot({ path: 'test-results/maximize-02-fullscreen.png', fullPage: true }); + + // Check console logs for size + const sizeLogs = testLogger.consoleLogs.filter(l => l.includes('[MAXIMIZE_TEST] Window size:')); + expect(sizeLogs.length).toBeGreaterThan(0); + + // Parse window size from log + const lastSizeLog = sizeLogs[sizeLogs.length - 1]; + const sizeMatch = lastSizeLog.match(/Window size: (\d+)x(\d+)/); + expect(sizeMatch, 'Size log should contain dimensions').not.toBeNull(); + + if (sizeMatch) { + const width = parseInt(sizeMatch[1]); + const height = parseInt(sizeMatch[2]); + + // Window should be larger than 100px if maximize worked + // This is the key assertion - KiCad bug results in 20x20 or 30x20 windows + expect(width, 'Window width should be > 100px (got ' + width + ')').toBeGreaterThan(100); + expect(height, 'Window height should be > 100px (got ' + height + ')').toBeGreaterThan(100); + } + + // Check for PASS/FAIL log + const passLog = testLogger.consoleLogs.some(l => l.includes('[MAXIMIZE_TEST] PASS')); + const failLog = testLogger.consoleLogs.some(l => l.includes('[MAXIMIZE_TEST] FAIL')); + + expect(failLog, 'Should not have FAIL log').toBe(false); + expect(passLog, 'Should have PASS log').toBe(true); + }); + + // Note: wxDisplay::GetFromWindow() has a bug returning invalid display index + // This is a separate issue from the Maximize() functionality being tested + test.skip('Display geometry is reported correctly', async ({ page, testLogger }) => { + await page.goto('/standalone/maximize/maximize_test.html'); + const loaded = await tryLoadApp(page); + if (!loaded) { + test.skip(); + return; + } + + await page.waitForTimeout(500); + + // Check display geometry logs + const geomLogs = testLogger.consoleLogs.filter(l => l.includes('[MAXIMIZE_TEST] Display geometry:')); + expect(geomLogs.length).toBeGreaterThan(0); + + // Parse display geometry + const geomLog = geomLogs[0]; + const geomMatch = geomLog.match(/Display geometry: (\d+)x(\d+)/); + expect(geomMatch, 'Geometry log should contain dimensions').not.toBeNull(); + + if (geomMatch) { + const displayWidth = parseInt(geomMatch[1]); + const displayHeight = parseInt(geomMatch[2]); + + // Display should report reasonable viewport size + expect(displayWidth, 'Display width should be > 100px').toBeGreaterThan(100); + expect(displayHeight, 'Display height should be > 100px').toBeGreaterThan(100); + } + }); + + test('Canvas is properly sized after maximize', async ({ page, testLogger }) => { + await page.goto('/standalone/maximize/maximize_test.html'); + const loaded = await tryLoadApp(page); + if (!loaded) { + test.skip(); + return; + } + + await page.waitForTimeout(500); + + const box = await getCanvasBox(page); + + await page.screenshot({ path: 'test-results/maximize-03-canvas.png', fullPage: true }); + + // Canvas should be reasonably sized (not tiny) + expect(box.width, 'Canvas width should be > 100px').toBeGreaterThan(100); + expect(box.height, 'Canvas height should be > 100px').toBeGreaterThan(100); + }); +}); diff --git a/tests/global-setup.ts b/tests/global-setup.ts new file mode 100644 index 0000000..a85fc4c --- /dev/null +++ b/tests/global-setup.ts @@ -0,0 +1,22 @@ +import * as fs from 'fs'; +import * as path from 'path'; + +/** + * Global setup for Playwright tests. + * Cleans the logs directory before each test run to prevent stale logs. + */ +export default async function globalSetup() { + const logsDir = path.join(__dirname, 'logs'); + + if (fs.existsSync(logsDir)) { + // Remove all files in logs directory + for (const file of fs.readdirSync(logsDir)) { + const filePath = path.join(logsDir, file); + // Only remove files, not subdirectories + if (fs.statSync(filePath).isFile()) { + fs.unlinkSync(filePath); + } + } + console.log(`[global-setup] Cleaned ${logsDir}`); + } +} diff --git a/tests/playwright.config.ts b/tests/playwright.config.ts index 8cbfbaa..021032e 100644 --- a/tests/playwright.config.ts +++ b/tests/playwright.config.ts @@ -46,6 +46,7 @@ function findFreePort(): number { const port = getOrFindPort(); export default defineConfig({ + globalSetup: './global-setup.ts', testDir: './e2e', fullyParallel: true, forbidOnly: !!process.env.CI, diff --git a/tests/wasm-app/Makefile.wasm b/tests/wasm-app/Makefile.wasm index c3f21df..0cd6c2c 100644 --- a/tests/wasm-app/Makefile.wasm +++ b/tests/wasm-app/Makefile.wasm @@ -130,7 +130,8 @@ all: minimal_test.html \ $(S)/fontenum/fontenum_test.html \ $(S)/textdecor/textdecor_test.html \ $(S)/bitmask/bitmask_test.html \ - $(S)/regions/regions_test.html + $(S)/regions/regions_test.html \ + $(S)/maximize/maximize_test.html # Main test app (uses GL) minimal_test.o: minimal_test.cpp @@ -405,6 +406,13 @@ $(S)/regions/regions_test.o: $(S)/regions/regions_test.cpp $(S)/regions/regions_test.html: $(S)/regions/regions_test.o $(CXX) $< $(LDFLAGS_NOGL) --pre-js $(JS) --shell-file $(HTML) -o $@ +# Maximize test (no GL) - reproduces KiCad startup maximize issue +$(S)/maximize/maximize_test.o: $(S)/maximize/maximize_test.cpp + $(CXX) -c $(CXXFLAGS) $< -o $@ + +$(S)/maximize/maximize_test.html: $(S)/maximize/maximize_test.o + $(CXX) $< $(LDFLAGS_NOGL) --pre-js $(JS) --shell-file $(HTML) -o $@ + # Convenience targets menu: $(S)/menu/menu_test.html clipboard: $(S)/clipboard/clipboard_test.html @@ -444,9 +452,10 @@ fontenum: $(S)/fontenum/fontenum_test.html textdecor: $(S)/textdecor/textdecor_test.html bitmask: $(S)/bitmask/bitmask_test.html regions: $(S)/regions/regions_test.html +maximize: $(S)/maximize/maximize_test.html clean: rm -f minimal_test.o minimal_test.html minimal_test.js minimal_test.wasm rm -f $(S)/*/*.o $(S)/*/*.html $(S)/*/*.js $(S)/*/*.wasm -.PHONY: all clean menu clipboard filedialog layout aui toolbar grid dialog timer tree dataview htmlwin stc print dnd propgrid pickers collapsible listctrl infobar dataviewvirtual auinotebook wizard gridedit calendar gridrenderers printpreview bitmapbuttons specialized validators ownerdrawn popup xml wasmedge fontenum textdecor bitmask regions +.PHONY: all clean menu clipboard filedialog layout aui toolbar grid dialog timer tree dataview htmlwin stc print dnd propgrid pickers collapsible listctrl infobar dataviewvirtual auinotebook wizard gridedit calendar gridrenderers printpreview bitmapbuttons specialized validators ownerdrawn popup xml wasmedge fontenum textdecor bitmask regions maximize diff --git a/tests/wasm-app/kicad/pcbnew.html b/tests/wasm-app/kicad/pcbnew.html index 01febb1..6b4869b 100644 --- a/tests/wasm-app/kicad/pcbnew.html +++ b/tests/wasm-app/kicad/pcbnew.html @@ -55,7 +55,7 @@
- +