From 19ffa220e8ea9d239a3a07c687e62598a51df153 Mon Sep 17 00:00:00 2001 From: Jonathan Tran Date: Fri, 18 Oct 2024 10:43:01 -0400 Subject: [PATCH] Fix reading files from WebAssembly (#4183) --- e2e/playwright/editor-tests.spec.ts | 92 ++++++++++++++++++++++++++++- e2e/playwright/test-utils.ts | 8 +++ interface.d.ts | 2 +- src/components/FileTree.tsx | 2 +- src/lang/std/fileSystemManager.ts | 2 +- src/lib/desktop.ts | 12 +++- src/lib/routeLoaders.ts | 4 +- src/preload.ts | 2 +- 8 files changed, 115 insertions(+), 9 deletions(-) diff --git a/e2e/playwright/editor-tests.spec.ts b/e2e/playwright/editor-tests.spec.ts index f36232a915..b787118a58 100644 --- a/e2e/playwright/editor-tests.spec.ts +++ b/e2e/playwright/editor-tests.spec.ts @@ -1,6 +1,16 @@ import { test, expect } from '@playwright/test' +import fsp from 'fs/promises' import { uuidv4 } from 'lib/utils' -import { getUtils, setup, tearDown } from './test-utils' +import { + darkModeBgColor, + darkModePlaneColorXZ, + executorInputPath, + getUtils, + setup, + setupElectron, + tearDown, +} from './test-utils' +import { join } from 'path' test.beforeEach(async ({ context, page }, testInfo) => { await setup(context, page, testInfo) @@ -974,4 +984,84 @@ test.describe('Editor tests', () => { |> close(%) |> extrude(5, %)`) }) + + test( + `Can use the import stdlib function on a local OBJ file`, + { tag: '@electron' }, + async ({ browserName }, testInfo) => { + const { electronApp, page } = await setupElectron({ + testInfo, + folderSetupFn: async (dir) => { + const bracketDir = join(dir, 'cube') + await fsp.mkdir(bracketDir, { recursive: true }) + await fsp.copyFile( + executorInputPath('cube.obj'), + join(bracketDir, 'cube.obj') + ) + await fsp.writeFile(join(bracketDir, 'main.kcl'), '') + }, + }) + const viewportSize = { width: 1200, height: 500 } + await page.setViewportSize(viewportSize) + + // Locators and constants + const u = await getUtils(page) + const projectLink = page.getByRole('link', { name: 'cube' }) + const gizmo = page.locator('[aria-label*=gizmo]') + const resetCameraButton = page.getByRole('button', { name: 'Reset view' }) + const locationToHavColor = async ( + position: { x: number; y: number }, + color: [number, number, number] + ) => { + return u.getGreatestPixDiff(position, color) + } + const notTheOrigin = { + x: viewportSize.width * 0.55, + y: viewportSize.height * 0.3, + } + const origin = { x: viewportSize.width / 2, y: viewportSize.height / 2 } + const errorIndicators = page.locator('.cm-lint-marker-error') + + await test.step(`Open the empty file, see the default planes`, async () => { + await projectLink.click() + await u.waitForPageLoad() + await expect + .poll( + async () => locationToHavColor(notTheOrigin, darkModePlaneColorXZ), + { + timeout: 5000, + message: 'XZ plane color is visible', + } + ) + .toBeLessThan(15) + }) + await test.step(`Write the import function line`, async () => { + await u.codeLocator.fill(`import('cube.obj')`) + await page.waitForTimeout(800) + }) + await test.step(`Reset the camera before checking`, async () => { + await u.doAndWaitForCmd(async () => { + await gizmo.click({ button: 'right' }) + await resetCameraButton.click() + }, 'zoom_to_fit') + }) + await test.step(`Verify that we see the imported geometry and no errors`, async () => { + await expect(errorIndicators).toHaveCount(0) + await expect + .poll(async () => locationToHavColor(origin, darkModePlaneColorXZ), { + timeout: 3000, + message: 'Plane color should not be visible', + }) + .toBeGreaterThan(15) + await expect + .poll(async () => locationToHavColor(origin, darkModeBgColor), { + timeout: 3000, + message: 'Background color should not be visible', + }) + .toBeGreaterThan(15) + }) + + await electronApp.close() + } + ) }) diff --git a/e2e/playwright/test-utils.ts b/e2e/playwright/test-utils.ts index 55b05a24ab..1cdbea456b 100644 --- a/e2e/playwright/test-utils.ts +++ b/e2e/playwright/test-utils.ts @@ -47,6 +47,14 @@ export const commonPoints = { num2: 14.44, } +/** A semi-reliable color to check the default XZ plane on + * in dark mode in the default camera position + */ +export const darkModePlaneColorXZ: [number, number, number] = [50, 50, 99] + +/** A semi-reliable color to check the default dark mode bg color against */ +export const darkModeBgColor: [number, number, number] = [27, 27, 27] + export const editorSelector = '[role="textbox"][data-language="kcl"]' type PaneId = 'variables' | 'code' | 'files' | 'logs' diff --git a/interface.d.ts b/interface.d.ts index e0f07c3769..9953d8c610 100644 --- a/interface.d.ts +++ b/interface.d.ts @@ -23,8 +23,8 @@ export interface IElectronAPI { key: string, callback: (eventType: string, path: string) => void ) => void + readFile: typeof fs.readFile watchFileOff: (path: string, key: string) => void - readFile: (path: string) => ReturnType writeFile: ( path: string, data: string | Uint8Array diff --git a/src/components/FileTree.tsx b/src/components/FileTree.tsx index c24f9a60a9..000ecd126f 100644 --- a/src/components/FileTree.tsx +++ b/src/components/FileTree.tsx @@ -140,7 +140,7 @@ const FileTreeItem = ({ async (eventType, path) => { // Don't try to read a file that was removed. if (isCurrentFile && eventType !== 'unlink') { - let code = await window.electron.readFile(path) + let code = await window.electron.readFile(path, { encoding: 'utf-8' }) code = normalizeLineEndings(code) codeManager.updateCodeStateEditor(code) } diff --git a/src/lang/std/fileSystemManager.ts b/src/lang/std/fileSystemManager.ts index 0bb71ed1a0..02b6be43c1 100644 --- a/src/lang/std/fileSystemManager.ts +++ b/src/lang/std/fileSystemManager.ts @@ -18,7 +18,7 @@ class FileSystemManager { return Promise.resolve(window.electron.path.join(dir, path)) } - async readFile(path: string): Promise { + async readFile(path: string): Promise { // Using local file system only works from desktop. if (!isDesktop()) { return Promise.reject( diff --git a/src/lib/desktop.ts b/src/lib/desktop.ts index 327733ebb5..247c36dd2b 100644 --- a/src/lib/desktop.ts +++ b/src/lib/desktop.ts @@ -448,7 +448,9 @@ export const readProjectSettingsFile = async ( } } - const configToml = await window.electron.readFile(settingsPath) + const configToml = await window.electron.readFile(settingsPath, { + encoding: 'utf-8', + }) const configObj = parseProjectSettings(configToml) if (err(configObj)) { return Promise.reject(configObj) @@ -467,7 +469,9 @@ export const readAppSettingsFile = async () => { // The file exists, read it and parse it. if (window.electron.exists(settingsPath)) { - const configToml = await window.electron.readFile(settingsPath) + const configToml = await window.electron.readFile(settingsPath, { + encoding: 'utf-8', + }) const parsedAppConfig = parseAppSettings(configToml) if (err(parsedAppConfig)) { return Promise.reject(parsedAppConfig) @@ -527,7 +531,9 @@ export const readTokenFile = async () => { let settingsPath = await getTokenFilePath() if (window.electron.exists(settingsPath)) { - const token: string = await window.electron.readFile(settingsPath) + const token: string = await window.electron.readFile(settingsPath, { + encoding: 'utf-8', + }) if (!token) return '' return token diff --git a/src/lib/routeLoaders.ts b/src/lib/routeLoaders.ts index 45f1d9c84c..14e4d5c4ff 100644 --- a/src/lib/routeLoaders.ts +++ b/src/lib/routeLoaders.ts @@ -109,7 +109,9 @@ export const fileLoader: LoaderFunction = async ( ) } - code = await window.electron.readFile(currentFilePath) + code = await window.electron.readFile(currentFilePath, { + encoding: 'utf-8', + }) code = normalizeLineEndings(code) // Update both the state and the editor's code. diff --git a/src/preload.ts b/src/preload.ts index d032e327fb..17ba4dd3cd 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -74,7 +74,7 @@ const watchFileOff = (path: string, key: string) => { fsWatchListeners.set(path, watchers) } } -const readFile = (path: string) => fs.readFile(path, 'utf-8') +const readFile = fs.readFile // It seems like from the node source code this does not actually block but also // don't trust me on that (jess). const exists = (path: string) => fsSync.existsSync(path)