diff --git a/README.md b/README.md index 5ab4363..0e21ce4 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ name as the package unless otherwise specified: - `coreutils`: multiple core commands including `cat`, `cp`, `echo`, `ls`, `mkdir`, `mv`, `rm`, `touch`, `uname`, and `wc` - `grep` - `lua` +- `tree` - `vim` ## Build diff --git a/demo/cockle-config-in.json b/demo/cockle-config-in.json index 99dbd5e..ca4bf36 100644 --- a/demo/cockle-config-in.json +++ b/demo/cockle-config-in.json @@ -6,6 +6,9 @@ "package": "local-cmd", "local_directory": "../test/local-packages" }, + { + "package": "tree" + }, { "package": "vim" } diff --git a/demo/ui-tests/demo.test.ts b/demo/ui-tests/demo.test.ts index 2d85f52..8336639 100644 --- a/demo/ui-tests/demo.test.ts +++ b/demo/ui-tests/demo.test.ts @@ -21,9 +21,6 @@ test('visual test', async ({ page }) => { await page.locator('div.xterm-screen').click(); // sets focus for keyboard input - await inputLine(page, 'ls'); // avoid timestamps - await page.waitForTimeout(wait); - await inputLine(page, 'cp file.txt file2.txt'); await page.waitForTimeout(wait); diff --git a/demo/ui-tests/demo.test.ts-snapshots/visual-test-1-chromium-linux.png b/demo/ui-tests/demo.test.ts-snapshots/visual-test-1-chromium-linux.png index 421f74e..522d92d 100644 Binary files a/demo/ui-tests/demo.test.ts-snapshots/visual-test-1-chromium-linux.png and b/demo/ui-tests/demo.test.ts-snapshots/visual-test-1-chromium-linux.png differ diff --git a/src/buffered_io.ts b/src/buffered_io.ts index d8d6ffe..159a3d1 100644 --- a/src/buffered_io.ts +++ b/src/buffered_io.ts @@ -65,6 +65,13 @@ abstract class BufferedIO { return this._enabled; } + utf8ArrayToString(chars: Int8Array): string { + if (this._utf8Decoder === undefined) { + this._utf8Decoder = new TextDecoder('utf8'); + } + return this._utf8Decoder.decode(chars); + } + protected _clear() { this._readArray[READ_MAIN] = 0; this._readArray[READ_WORKER] = 0; @@ -92,6 +99,8 @@ abstract class BufferedIO { protected _maxWriteChars: number = 256; // Multiples of this can be sent consecutively. protected _writeArray: Int32Array; + + private _utf8Decoder?: TextDecoder; } export namespace MainBufferedIO { diff --git a/src/commands/wasm_command_runner.ts b/src/commands/wasm_command_runner.ts index ae81fe7..a3a2ea9 100644 --- a/src/commands/wasm_command_runner.ts +++ b/src/commands/wasm_command_runner.ts @@ -74,7 +74,7 @@ export abstract class WasmCommandRunner implements ICommandRunner { } const chars = buffer.slice(offset, offset + length); - const text = String.fromCharCode(...chars); + const text = bufferedIO.utf8ArrayToString(chars); const isStderr = stream.path === '/dev/tty1'; if (isStderr && cmdName === 'touch' && args.length > 1) { diff --git a/test/cockle-config-in.json b/test/cockle-config-in.json index 2405d5e..10a5b16 100644 --- a/test/cockle-config-in.json +++ b/test/cockle-config-in.json @@ -2,6 +2,9 @@ { "package": "lua" }, + { + "package": "tree" + }, { "package": "vim" }, diff --git a/test/tests/command/tree.test.ts b/test/tests/command/tree.test.ts new file mode 100644 index 0000000..24dc18b --- /dev/null +++ b/test/tests/command/tree.test.ts @@ -0,0 +1,24 @@ +import { expect } from '@playwright/test'; +import { shellLineSimple, test } from '../utils'; + +test.describe('tree command', () => { + test('should write version', async ({ page }) => { + const output = await shellLineSimple(page, 'tree --version'); + expect(output).toMatch( + '\r\ntree v2.2.1 © 1996 - 2024 by Steve Baker, Thomas Moore, Francesc Rocher, Florian Sesser, Kyosuke Tokoro\r\n' + ); + }); + + test('should write tree', async ({ page }) => { + const output = await shellLineSimple(page, 'tree'); + expect(output).toMatch( + 'tree\r\n' + + '.\r\n' + + '├── dirA\r\n' + + '├── file1\r\n' + + '└── file2\r\n' + + '\r\n' + + '2 directories, 2 files\r\n' + ); + }); +}); diff --git a/test/tests/shell.test.ts b/test/tests/shell.test.ts index d433e4f..d562bd6 100644 --- a/test/tests/shell.test.ts +++ b/test/tests/shell.test.ts @@ -157,6 +157,11 @@ test.describe('Shell', () => { expect(output[14]).toMatch(/Error: cd: too many arguments/); expect(output[15]).toMatch('\r\n?=1\r\n'); }); + + test('should support unicode', async ({ page }) => { + const output = await shellLineSimple(page, 'echo 🚀'); + expect(output).toMatch(/^echo 🚀\r\n🚀\r\n/); + }); }); test.describe('echo input', () => { @@ -207,8 +212,8 @@ test.describe('Shell', () => { const ret1 = output.textAndClear(); return [ret0, ret1]; }); - expect(output[0]).toMatch(/^t\r\ntail\r\ntouch\r\ntr\r\ntty\r\n/); - expect(output[1]).toMatch(/^\r\ntail {3}tr\r\ntouch {2}tty\r\n/); + expect(output[0]).toMatch(/^t\r\ntail\r\ntouch\r\ntr\r\ntree\r\ntty\r\n/); + expect(output[1]).toMatch(/^\r\ntail {3}tree\r\ntouch {2}tty\r\ntr\r\n/); }); test('should add common startsWith', async ({ page }) => {