00a83ce4ac
Remove redundant code-view cancellation and align button labels. Restore module scrollbar thickness while preserving workspace colors, and support config, SDF and world text files locally and remotely.
79 lines
5.2 KiB
JavaScript
79 lines
5.2 KiB
JavaScript
const { test } = require('node:test')
|
|
const assert = require('node:assert/strict')
|
|
const fs = require('node:fs/promises')
|
|
const path = require('node:path')
|
|
const os = require('node:os')
|
|
const vm = require('node:vm')
|
|
const ts = require('typescript')
|
|
const { execFile } = require('node:child_process')
|
|
const { promisify } = require('node:util')
|
|
const exec = promisify(execFile)
|
|
const root = path.resolve(__dirname, '..')
|
|
const placeholder = 'Binary or large file preview is not available.'
|
|
const xml = '<?xml version="1.0"?>\n<model><name>mkmini_gazebo</name><description>주행 모델</description></model>\n'
|
|
|
|
function compile(source, context = {}) {
|
|
const exports = {}
|
|
vm.runInNewContext(ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 } }).outputText, { exports, require, Buffer, ...context })
|
|
return exports
|
|
}
|
|
|
|
async function fixture(run) {
|
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'lms-xml-editor-'))
|
|
try {
|
|
for (const name of ['model.config', 'model.sdf', 'simulation.world', 'MODEL.CONFIG']) await fs.writeFile(path.join(dir, name), xml)
|
|
await fs.writeFile(path.join(dir, 'large.config'), 'x'.repeat(1024 * 1024 + 1))
|
|
await fs.writeFile(path.join(dir, 'model.glb'), Buffer.from([0, 1, 2, 3]))
|
|
await run(dir)
|
|
} finally { await fs.rm(dir, { recursive: true, force: true }) }
|
|
}
|
|
|
|
test('local module and root collectors read small Gazebo XML files without changing binary and size exclusions', async () => {
|
|
const source = ts.createSourceFile('index.ts', await fs.readFile(path.join(root, 'src/main/index.ts'), 'utf8'), ts.ScriptTarget.Latest, true)
|
|
const functions = source.statements.filter(node => ts.isFunctionDeclaration(node) && ['collectModuleFiles', 'collectRootFiles'].includes(node.name?.text))
|
|
const pattern = source.statements.find(node => ts.isVariableStatement(node) && node.declarationList.declarations[0].name.getText(source) === 'textFilePattern')
|
|
assert.equal(functions.length, 2)
|
|
assert.ok(pattern)
|
|
await fixture(async dir => {
|
|
const api = compile([pattern.getText(source), ...functions.map(node => `export ${node.getText(source)}`)].join('\n'), {
|
|
readdir: fs.readdir, stat: fs.stat, readFile: fs.readFile,
|
|
resolve: path.resolve, basename: path.basename,
|
|
skippedCacheDirs: new Set(['.git', 'build', 'install', 'log', 'node_modules', 'docker']),
|
|
findModuleRoot: async () => dir,
|
|
toRendererModulePath: (name, file, base) => `modules/${name}/${path.relative(base, file)}`,
|
|
toRendererRootPath: (name, file, base) => `${name}/${path.relative(base, file)}`
|
|
})
|
|
for (const result of [await api.collectModuleFiles('NAV2-MKMini'), await api.collectRootFiles(dir, 'examples')]) {
|
|
const files = Object.fromEntries(Object.entries(result.files).map(([file, content]) => [path.basename(file), content]))
|
|
for (const name of ['model.config', 'model.sdf', 'simulation.world', 'MODEL.CONFIG']) assert.equal(files[name], xml)
|
|
for (const name of ['large.config', 'model.glb']) assert.equal(files[name], placeholder)
|
|
}
|
|
})
|
|
})
|
|
|
|
test('remote listing, content and search include Gazebo XML while preserving size, NUL and path protections', async () => {
|
|
const api = compile(await fs.readFile(path.join(root, 'src/main/remoteModuleFiles.ts'), 'utf8'))
|
|
await fixture(async dir => {
|
|
const request = async input => JSON.parse((await exec('bash', ['-c', api.remoteModuleFilesScript(dir, input)], { timeout: 10000 })).stdout)
|
|
const listing = await request({ action: 'list' })
|
|
assert.ok(listing.filePaths.includes('model.config'))
|
|
for (const file of ['model.config', 'model.sdf', 'simulation.world', 'MODEL.CONFIG']) assert.equal((await request({ action: 'content', path: file })).content, xml)
|
|
for (const file of ['large.config', 'model.glb']) assert.equal((await request({ action: 'content', path: file })).content, placeholder)
|
|
await fs.writeFile(path.join(dir, 'binary.config'), Buffer.from('text\0binary'))
|
|
assert.equal((await request({ action: 'content', path: 'binary.config' })).content, placeholder)
|
|
const matches = await request({ action: 'search', query: 'mkmini_gazebo' })
|
|
assert.deepEqual(matches.map(match => match.file).sort(), ['MODEL.CONFIG', 'model.config', 'model.sdf', 'simulation.world'].sort())
|
|
await assert.rejects(request({ action: 'content', path: '../outside.config' }), /Invalid module path/)
|
|
await fs.symlink(path.join(dir, '..'), path.join(dir, 'escape'))
|
|
await assert.rejects(request({ action: 'content', path: 'escape/outside.config' }), /Invalid module path/)
|
|
})
|
|
})
|
|
|
|
test('Gazebo model.config, SDF and world files use XML highlighting without treating arbitrary config formats as XML', async () => {
|
|
const api = compile(await fs.readFile(path.join(root, 'src/renderer/src/features/workspace/workspaceFiles.ts'), 'utf8'))
|
|
for (const file of ['model.config', 'models/mkmini/model.config', 'models/MODEL.CONFIG', 'model.sdf', 'simulation.world', 'WORLD.SDF', 'package.xml', 'robot.urdf', 'robot.xacro']) assert.equal(api.getEditorLanguage(file), 'xml', file)
|
|
assert.equal(api.getEditorLanguage('app.config'), 'plaintext')
|
|
assert.equal(api.getEditorLanguage('control.yaml'), 'yaml')
|
|
assert.equal(api.getEditorLanguage('model.glb'), 'plaintext')
|
|
})
|