Fix reading files from WebAssembly (#4183)
This commit is contained in:
@ -1,6 +1,16 @@
|
|||||||
import { test, expect } from '@playwright/test'
|
import { test, expect } from '@playwright/test'
|
||||||
|
import fsp from 'fs/promises'
|
||||||
import { uuidv4 } from 'lib/utils'
|
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) => {
|
test.beforeEach(async ({ context, page }, testInfo) => {
|
||||||
await setup(context, page, testInfo)
|
await setup(context, page, testInfo)
|
||||||
@ -974,4 +984,84 @@ test.describe('Editor tests', () => {
|
|||||||
|> close(%)
|
|> close(%)
|
||||||
|> extrude(5, %)`)
|
|> 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()
|
||||||
|
}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
@ -47,6 +47,14 @@ export const commonPoints = {
|
|||||||
num2: 14.44,
|
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"]'
|
export const editorSelector = '[role="textbox"][data-language="kcl"]'
|
||||||
type PaneId = 'variables' | 'code' | 'files' | 'logs'
|
type PaneId = 'variables' | 'code' | 'files' | 'logs'
|
||||||
|
|
||||||
|
2
interface.d.ts
vendored
2
interface.d.ts
vendored
@ -23,8 +23,8 @@ export interface IElectronAPI {
|
|||||||
key: string,
|
key: string,
|
||||||
callback: (eventType: string, path: string) => void
|
callback: (eventType: string, path: string) => void
|
||||||
) => void
|
) => void
|
||||||
|
readFile: typeof fs.readFile
|
||||||
watchFileOff: (path: string, key: string) => void
|
watchFileOff: (path: string, key: string) => void
|
||||||
readFile: (path: string) => ReturnType<fs.readFile>
|
|
||||||
writeFile: (
|
writeFile: (
|
||||||
path: string,
|
path: string,
|
||||||
data: string | Uint8Array
|
data: string | Uint8Array
|
||||||
|
@ -140,7 +140,7 @@ const FileTreeItem = ({
|
|||||||
async (eventType, path) => {
|
async (eventType, path) => {
|
||||||
// Don't try to read a file that was removed.
|
// Don't try to read a file that was removed.
|
||||||
if (isCurrentFile && eventType !== 'unlink') {
|
if (isCurrentFile && eventType !== 'unlink') {
|
||||||
let code = await window.electron.readFile(path)
|
let code = await window.electron.readFile(path, { encoding: 'utf-8' })
|
||||||
code = normalizeLineEndings(code)
|
code = normalizeLineEndings(code)
|
||||||
codeManager.updateCodeStateEditor(code)
|
codeManager.updateCodeStateEditor(code)
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ class FileSystemManager {
|
|||||||
return Promise.resolve(window.electron.path.join(dir, path))
|
return Promise.resolve(window.electron.path.join(dir, path))
|
||||||
}
|
}
|
||||||
|
|
||||||
async readFile(path: string): Promise<Uint8Array | void> {
|
async readFile(path: string): Promise<Uint8Array> {
|
||||||
// Using local file system only works from desktop.
|
// Using local file system only works from desktop.
|
||||||
if (!isDesktop()) {
|
if (!isDesktop()) {
|
||||||
return Promise.reject(
|
return Promise.reject(
|
||||||
|
@ -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)
|
const configObj = parseProjectSettings(configToml)
|
||||||
if (err(configObj)) {
|
if (err(configObj)) {
|
||||||
return Promise.reject(configObj)
|
return Promise.reject(configObj)
|
||||||
@ -467,7 +469,9 @@ export const readAppSettingsFile = async () => {
|
|||||||
|
|
||||||
// The file exists, read it and parse it.
|
// The file exists, read it and parse it.
|
||||||
if (window.electron.exists(settingsPath)) {
|
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)
|
const parsedAppConfig = parseAppSettings(configToml)
|
||||||
if (err(parsedAppConfig)) {
|
if (err(parsedAppConfig)) {
|
||||||
return Promise.reject(parsedAppConfig)
|
return Promise.reject(parsedAppConfig)
|
||||||
@ -527,7 +531,9 @@ export const readTokenFile = async () => {
|
|||||||
let settingsPath = await getTokenFilePath()
|
let settingsPath = await getTokenFilePath()
|
||||||
|
|
||||||
if (window.electron.exists(settingsPath)) {
|
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 ''
|
if (!token) return ''
|
||||||
|
|
||||||
return token
|
return token
|
||||||
|
@ -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)
|
code = normalizeLineEndings(code)
|
||||||
|
|
||||||
// Update both the state and the editor's code.
|
// Update both the state and the editor's code.
|
||||||
|
@ -74,7 +74,7 @@ const watchFileOff = (path: string, key: string) => {
|
|||||||
fsWatchListeners.set(path, watchers)
|
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
|
// It seems like from the node source code this does not actually block but also
|
||||||
// don't trust me on that (jess).
|
// don't trust me on that (jess).
|
||||||
const exists = (path: string) => fsSync.existsSync(path)
|
const exists = (path: string) => fsSync.existsSync(path)
|
||||||
|
Reference in New Issue
Block a user