mirror of
https://github.com/simonw/datasette.git
synced 2026-06-19 23:34:34 +02:00
parent
3cfdca026a
commit
387e309b3b
2 changed files with 125 additions and 202 deletions
|
|
@ -1,202 +0,0 @@
|
|||
import json
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import textwrap
|
||||
|
||||
STATIC_DIR = Path(__file__).resolve().parents[1] / "datasette" / "static"
|
||||
|
||||
|
||||
def test_builtin_json_column_field_validation():
|
||||
script = textwrap.dedent("""
|
||||
const fs = require("fs");
|
||||
const vm = require("vm");
|
||||
const editToolsJs = __EDIT_TOOLS_JS__;
|
||||
|
||||
class FakeEvent {
|
||||
constructor(type, options) {
|
||||
this.type = type;
|
||||
this.bubbles = !!(options && options.bubbles);
|
||||
}
|
||||
}
|
||||
|
||||
class FakeElement {
|
||||
constructor(tagName = "div") {
|
||||
this.nodeName = tagName.toUpperCase();
|
||||
this.nodeType = 1;
|
||||
this.children = [];
|
||||
this.dataset = {};
|
||||
this.attributes = {};
|
||||
this.value = "";
|
||||
this.name = "";
|
||||
this.disabled = false;
|
||||
this.hidden = false;
|
||||
this.textContent = "";
|
||||
this.validationMessage = "";
|
||||
this.eventListeners = {};
|
||||
this.className = "";
|
||||
}
|
||||
appendChild(child) {
|
||||
this.children.push(child);
|
||||
child.parentNode = this;
|
||||
return child;
|
||||
}
|
||||
addEventListener(type, callback) {
|
||||
this.eventListeners[type] = this.eventListeners[type] || [];
|
||||
this.eventListeners[type].push(callback);
|
||||
}
|
||||
dispatchEvent(event) {
|
||||
event.target = event.target || this;
|
||||
for (const callback of this.eventListeners[event.type] || []) {
|
||||
callback(event);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
setAttribute(name, value) {
|
||||
this.attributes[name] = String(value);
|
||||
}
|
||||
getAttribute(name) {
|
||||
return this.attributes[name] || null;
|
||||
}
|
||||
removeAttribute(name) {
|
||||
delete this.attributes[name];
|
||||
}
|
||||
setCustomValidity(message) {
|
||||
this.validationMessage = message;
|
||||
}
|
||||
}
|
||||
|
||||
global.Event = FakeEvent;
|
||||
global.document = {
|
||||
addEventListener() {},
|
||||
createElement(tagName) {
|
||||
return new FakeElement(tagName);
|
||||
},
|
||||
createTextNode(text) {
|
||||
const node = new FakeElement("#text");
|
||||
node.textContent = text;
|
||||
return node;
|
||||
},
|
||||
};
|
||||
global.location = {
|
||||
href: "http://localhost/data/projects",
|
||||
pathname: "/data/projects",
|
||||
search: "",
|
||||
};
|
||||
global.window = {
|
||||
_datasetteTableData: {
|
||||
database: "data",
|
||||
table: "projects",
|
||||
tableUrl: "/data/projects",
|
||||
},
|
||||
};
|
||||
|
||||
vm.runInThisContext(fs.readFileSync(editToolsJs, "utf8"), {
|
||||
filename: "edit-tools.js",
|
||||
});
|
||||
|
||||
const plugins = [];
|
||||
registerBuiltinColumnFieldPlugins({
|
||||
registerPlugin(name, plugin) {
|
||||
plugins.push({ name, plugin });
|
||||
},
|
||||
});
|
||||
const jsonPlugin = plugins.find((entry) => entry.name === "datasette-json-column");
|
||||
if (!jsonPlugin) {
|
||||
throw new Error("datasette-json-column plugin was not registered");
|
||||
}
|
||||
const pluginControl = jsonPlugin.plugin.makeColumnField({
|
||||
column: "metadata",
|
||||
columnType: { type: "json", config: {} },
|
||||
});
|
||||
if (!pluginControl || pluginControl.useTextarea !== true) {
|
||||
throw new Error("JSON column plugin should request a textarea");
|
||||
}
|
||||
|
||||
const context = columnFormControlContext(
|
||||
"metadata",
|
||||
false,
|
||||
{ type: "json", config: {} },
|
||||
{ mode: "edit" }
|
||||
);
|
||||
const control = new FakeElement("textarea");
|
||||
control.name = "metadata";
|
||||
control.value = '{"ok": true}';
|
||||
control.dataset.initialValue = '{"ok": true}';
|
||||
control.dataset.initialValueKind = "string";
|
||||
control.dataset.currentValueKind = "string";
|
||||
const meta = new FakeElement("span");
|
||||
|
||||
const field = createColumnFieldApi({
|
||||
id: "row-edit-field-0",
|
||||
labelId: "row-edit-field-label-0",
|
||||
descriptionId: "row-edit-field-meta-0",
|
||||
control,
|
||||
meta,
|
||||
context,
|
||||
});
|
||||
renderColumnField(
|
||||
Object.assign({ pluginName: "datasette-json-column" }, pluginControl),
|
||||
field
|
||||
);
|
||||
|
||||
if (control.validationMessage !== "") {
|
||||
throw new Error("Initial valid JSON should not be invalid");
|
||||
}
|
||||
if (control.dataset.initialValueKind !== "string") {
|
||||
throw new Error("JSON plugin should keep the original string value kind");
|
||||
}
|
||||
if (control.dataset.currentValueKind !== "string") {
|
||||
throw new Error("JSON plugin should keep the current string value kind");
|
||||
}
|
||||
if (!field.validationMessageElement || field.validationMessageElement.hidden !== true) {
|
||||
throw new Error("JSON validation message should start hidden");
|
||||
}
|
||||
|
||||
control.value = "{";
|
||||
control.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
if (!control.validationMessage.startsWith("Invalid JSON")) {
|
||||
throw new Error("Invalid JSON should set a custom validity message");
|
||||
}
|
||||
if (control.getAttribute("aria-invalid") !== "true") {
|
||||
throw new Error("Invalid JSON should set aria-invalid");
|
||||
}
|
||||
if (field.validationMessageElement.hidden) {
|
||||
throw new Error("Invalid JSON should show the validation message");
|
||||
}
|
||||
|
||||
control.value = '{"ok": true}';
|
||||
control.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
if (control.validationMessage !== "") {
|
||||
throw new Error("Valid JSON should clear the custom validity message");
|
||||
}
|
||||
if (control.getAttribute("aria-invalid") !== null) {
|
||||
throw new Error("Valid JSON should clear aria-invalid");
|
||||
}
|
||||
if (!field.validationMessageElement.hidden) {
|
||||
throw new Error("Valid JSON should hide the validation message");
|
||||
}
|
||||
|
||||
control.dataset.initialValue = '{"ok":';
|
||||
control.value = '{"ok": true}';
|
||||
const values = collectRowFormValues({
|
||||
mode: "edit",
|
||||
fields: {
|
||||
querySelectorAll() {
|
||||
return [control];
|
||||
},
|
||||
},
|
||||
});
|
||||
if (values.metadata !== '{"ok": true}') {
|
||||
throw new Error("Corrected JSON should be submitted as a string value");
|
||||
}
|
||||
|
||||
process.stdout.write("ok");
|
||||
""").replace("__EDIT_TOOLS_JS__", json.dumps(str(STATIC_DIR / "edit-tools.js")))
|
||||
result = subprocess.run(
|
||||
["node", "-e", script],
|
||||
text=True,
|
||||
capture_output=True,
|
||||
check=False,
|
||||
)
|
||||
assert result.returncode == 0, result.stderr
|
||||
assert result.stdout == "ok"
|
||||
|
|
@ -542,3 +542,128 @@ def test_table_plugin_column_field_api(page, datasette_server):
|
|||
);
|
||||
}
|
||||
""")
|
||||
|
||||
|
||||
@pytest.mark.playwright
|
||||
def test_builtin_json_column_field_validation(page, datasette_server):
|
||||
load_edit_tools(page, datasette_server)
|
||||
page.evaluate("""
|
||||
() => {
|
||||
const assert = (condition, message) => {
|
||||
if (!condition) {
|
||||
throw new Error(message);
|
||||
}
|
||||
};
|
||||
|
||||
const plugins = [];
|
||||
registerBuiltinColumnFieldPlugins({
|
||||
registerPlugin(name, plugin) {
|
||||
plugins.push({ name, plugin });
|
||||
},
|
||||
});
|
||||
const jsonPlugin = plugins.find(
|
||||
(entry) => entry.name === "datasette-json-column",
|
||||
);
|
||||
assert(
|
||||
jsonPlugin,
|
||||
"datasette-json-column plugin was not registered",
|
||||
);
|
||||
const pluginControl = jsonPlugin.plugin.makeColumnField({
|
||||
column: "metadata",
|
||||
columnType: { type: "json", config: {} },
|
||||
});
|
||||
assert(
|
||||
pluginControl && pluginControl.useTextarea === true,
|
||||
"JSON column plugin should request a textarea",
|
||||
);
|
||||
|
||||
const context = columnFormControlContext(
|
||||
"metadata",
|
||||
false,
|
||||
{ type: "json", config: {} },
|
||||
{ mode: "edit" },
|
||||
);
|
||||
const control = document.createElement("textarea");
|
||||
control.className = "row-edit-input";
|
||||
control.name = "metadata";
|
||||
control.value = '{"ok": true}';
|
||||
control.dataset.initialValue = '{"ok": true}';
|
||||
control.dataset.initialValueKind = "string";
|
||||
control.dataset.currentValueKind = "string";
|
||||
const meta = document.createElement("span");
|
||||
|
||||
const field = createColumnFieldApi({
|
||||
id: "row-edit-field-0",
|
||||
labelId: "row-edit-field-label-0",
|
||||
descriptionId: "row-edit-field-meta-0",
|
||||
control,
|
||||
meta,
|
||||
context,
|
||||
});
|
||||
const wrapper = renderColumnField(
|
||||
Object.assign({ pluginName: "datasette-json-column" }, pluginControl),
|
||||
field,
|
||||
);
|
||||
|
||||
assert(
|
||||
control.validationMessage === "",
|
||||
"Initial valid JSON should not be invalid",
|
||||
);
|
||||
assert(
|
||||
control.dataset.initialValueKind === "string",
|
||||
"JSON plugin should keep the original string value kind",
|
||||
);
|
||||
assert(
|
||||
control.dataset.currentValueKind === "string",
|
||||
"JSON plugin should keep the current string value kind",
|
||||
);
|
||||
assert(
|
||||
field.validationMessageElement &&
|
||||
field.validationMessageElement.hidden === true,
|
||||
"JSON validation message should start hidden",
|
||||
);
|
||||
|
||||
control.value = "{";
|
||||
control.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
assert(
|
||||
control.validationMessage.startsWith("Invalid JSON"),
|
||||
"Invalid JSON should set a custom validity message",
|
||||
);
|
||||
assert(
|
||||
control.getAttribute("aria-invalid") === "true",
|
||||
"Invalid JSON should set aria-invalid",
|
||||
);
|
||||
assert(
|
||||
!field.validationMessageElement.hidden,
|
||||
"Invalid JSON should show the validation message",
|
||||
);
|
||||
|
||||
control.value = '{"ok": true}';
|
||||
control.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
assert(
|
||||
control.validationMessage === "",
|
||||
"Valid JSON should clear the custom validity message",
|
||||
);
|
||||
assert(
|
||||
control.getAttribute("aria-invalid") === null,
|
||||
"Valid JSON should clear aria-invalid",
|
||||
);
|
||||
assert(
|
||||
field.validationMessageElement.hidden,
|
||||
"Valid JSON should hide the validation message",
|
||||
);
|
||||
|
||||
control.dataset.initialValue = '{"ok":';
|
||||
control.value = '{"ok": true}';
|
||||
const fields = document.createElement("div");
|
||||
fields.appendChild(wrapper);
|
||||
const values = collectRowFormValues({
|
||||
mode: "edit",
|
||||
fields,
|
||||
});
|
||||
assert(
|
||||
values.metadata === '{"ok": true}',
|
||||
"Corrected JSON should be submitted as a string value",
|
||||
);
|
||||
}
|
||||
""")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue