feat: workspace helpers - quoting paths, what a dropped file does, with node tests
This commit is contained in:
1 parent
0ada0899cf
commit
7b9daa524e
4 files changed
+90
No files matched your search
@@ -434,6 +434,11 @@ func assetHashes(fsys fs.FS) map[string]string {
|
|||||||
func staticHandler(fsys fs.FS, hashes map[string]string) http.Handler {
|
func staticHandler(fsys fs.FS, hashes map[string]string) http.Handler {
|
||||||
files := http.FileServer(http.FS(fsys))
|
files := http.FileServer(http.FS(fsys))
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// The JS tests sit beside the scripts but are not served.
|
||||||
|
if strings.HasSuffix(r.URL.Path, ".test.js") {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
h := hashes[strings.TrimPrefix(r.URL.Path, "/")]
|
h := hashes[strings.TrimPrefix(r.URL.Path, "/")]
|
||||||
if h != "" {
|
if h != "" {
|
||||||
w.Header().Set("ETag", `"`+h+`"`)
|
w.Header().Set("ETag", `"`+h+`"`)
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
"testing/fstest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStaticHidesTests(t *testing.T) {
|
||||||
|
fsys := fstest.MapFS{
|
||||||
|
"wsutil.js": {Data: []byte("x")},
|
||||||
|
"wsutil.test.js": {Data: []byte("y")},
|
||||||
|
}
|
||||||
|
h := staticHandler(fsys, map[string]string{})
|
||||||
|
for path, want := range map[string]int{"/wsutil.js": 200, "/wsutil.test.js": 404} {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
h.ServeHTTP(w, httptest.NewRequest(http.MethodGet, path, nil))
|
||||||
|
if w.Code != want {
|
||||||
|
t.Errorf("%s: got %d, want %d", path, w.Code, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// Pure helpers of the workspace: quoting paths for a shell, what a dropped
|
||||||
|
// file does where it lands, sizes. No DOM here, so node can test them.
|
||||||
|
(function (root) {
|
||||||
|
'use strict';
|
||||||
|
function shellQuote(p) { return "'" + String(p).replace(/'/g, "'\\''") + "'"; }
|
||||||
|
function shellQuoteAll(ps) { return ps.map(shellQuote).join(' '); }
|
||||||
|
const same = (a, b) => a.kind === b.kind && a.id === b.id;
|
||||||
|
// dropAction says what dropping item on target does: a transfer between
|
||||||
|
// file panes, paths pasted into a terminal of the same place, nothing on
|
||||||
|
// its own folder, or a refusal with the reason.
|
||||||
|
function dropAction(item, target) {
|
||||||
|
if (target.kind === 'term') {
|
||||||
|
return same(item.source, target.source) ? {kind: 'paste'}
|
||||||
|
: {kind: 'deny', reason: 'Нельзя: файл на ' + item.source.label + ', а терминал на ' + target.source.label};
|
||||||
|
}
|
||||||
|
if (same(item.source, target.source) && target.dir === item.dir) return {kind: 'none'};
|
||||||
|
return {kind: 'transfer'};
|
||||||
|
}
|
||||||
|
function fmtSize(n) {
|
||||||
|
if (n < 1024) return n + ' B';
|
||||||
|
if (n < 1048576) return (n / 1024).toFixed(1) + ' K';
|
||||||
|
if (n < 1073741824) return (n / 1048576).toFixed(1) + ' M';
|
||||||
|
return (n / 1073741824).toFixed(1) + ' G';
|
||||||
|
}
|
||||||
|
const api = {shellQuote, shellQuoteAll, dropAction, fmtSize};
|
||||||
|
if (typeof module !== 'undefined') module.exports = api; else root.WSUtil = api;
|
||||||
|
})(this);
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
'use strict';
|
||||||
|
const test = require('node:test');
|
||||||
|
const assert = require('node:assert');
|
||||||
|
const U = require('./wsutil.js');
|
||||||
|
|
||||||
|
test('shellQuote keeps a path one literal word', () => {
|
||||||
|
assert.strictEqual(U.shellQuote('/srv/a b'), "'/srv/a b'");
|
||||||
|
assert.strictEqual(U.shellQuote("/x/it's"), "'/x/it'\\''s'");
|
||||||
|
assert.strictEqual(U.shellQuote('/x/$HOME;rm'), "'/x/$HOME;rm'");
|
||||||
|
assert.strictEqual(U.shellQuoteAll(['/a', '/b c']), "'/a' '/b c'");
|
||||||
|
});
|
||||||
|
|
||||||
|
const m1 = {kind: 'agent', id: 'm1', label: 'web'};
|
||||||
|
const m2 = {kind: 'agent', id: 'm2', label: 'db'};
|
||||||
|
const s1 = {kind: 'ssh', id: 'm1|root@h:22', label: 'root@h'};
|
||||||
|
const item = {source: m1, paths: ['/etc/x'], dir: '/etc'};
|
||||||
|
|
||||||
|
test('dropAction: files into a file pane is a transfer', () => {
|
||||||
|
assert.deepStrictEqual(U.dropAction(item, {kind: 'files', source: m2, dir: '/tmp'}), {kind: 'transfer'});
|
||||||
|
assert.deepStrictEqual(U.dropAction(item, {kind: 'folder', source: m1, dir: '/var'}), {kind: 'transfer'});
|
||||||
|
});
|
||||||
|
test('dropAction: into its own folder does nothing', () => {
|
||||||
|
assert.deepStrictEqual(U.dropAction(item, {kind: 'files', source: m1, dir: '/etc'}), {kind: 'none'});
|
||||||
|
});
|
||||||
|
test('dropAction: terminal of the same place pastes, of another is denied', () => {
|
||||||
|
assert.deepStrictEqual(U.dropAction(item, {kind: 'term', source: m1}), {kind: 'paste'});
|
||||||
|
const d = U.dropAction(item, {kind: 'term', source: m2});
|
||||||
|
assert.strictEqual(d.kind, 'deny');
|
||||||
|
assert.strictEqual(d.reason, 'Нельзя: файл на web, а терминал на db');
|
||||||
|
assert.strictEqual(U.dropAction({source: s1, paths: ['/a'], dir: '/'}, {kind: 'term', source: s1}).kind, 'paste');
|
||||||
|
});
|
||||||
|
test('fmtSize', () => {
|
||||||
|
assert.strictEqual(U.fmtSize(10), '10 B');
|
||||||
|
assert.strictEqual(U.fmtSize(2048), '2.0 K');
|
||||||
|
});
|
||||||
Reference in new issue
Block a user