mirror of
https://ghfast.top/https://github.com/discourse/discourse-shared-edits.git
synced 2026-07-15 11:17:01 +08:00
Major overhaul of the shared edits plugin to improve reliability, robustness, and developer experience: **Backend** - Extract Revise service for cleaner controller orchestration - Add StateValidator for base64/Yjs safety, corruption detection, and recovery - Centralize protocol constants (Ruby + JS) to avoid hardcoded strings - Add state hash sync verification and vector validation endpoints - Harden security (guardian checks), error handling, and resource cleanup - Resize shared edit columns migration; add state_hash column **Frontend** - Decompose shared-edit-manager into focused modules: yjs-document, markdown-sync, rich-mode-sync, network-manager, encoding-utils - Add cursor overlay and caret coordinate tracking for selection sharing - Add ProseMirror extension for rich-mode collaborative editing - Cache-busted Yjs bundle loading via hashed filenames - Fix scroll drift during sync **Testing & Tooling** - Extensive new specs: state_validator, revision_controller, model, revise service - New Ember acceptance tests: cursor, lifecycle, sync flows - Add support scripts: fake_writer (Playwright), state_corruptor, debug_recovery - Add support/lint wrapper for full CI lint suite - Update dependencies and rebuild Yjs/y-prosemirror bundles
122 lines
3.2 KiB
JavaScript
122 lines
3.2 KiB
JavaScript
import { click, visit, waitUntil } from "@ember/test-helpers";
|
|
import { test } from "qunit";
|
|
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
|
|
|
|
acceptance(`Discourse Shared Edits | Composer`, function (needs) {
|
|
let commitCalls;
|
|
|
|
needs.user();
|
|
needs.settings({
|
|
rich_editor: true,
|
|
});
|
|
|
|
needs.pretender((server, helper) => {
|
|
commitCalls = [];
|
|
|
|
server.put("/shared_edits/p/:id/enable.json", () =>
|
|
helper.response({ success: "OK" })
|
|
);
|
|
|
|
server.get("/posts/:id.json", () =>
|
|
helper.response({
|
|
id: 398,
|
|
raw: "initial post content",
|
|
})
|
|
);
|
|
|
|
server.get("/shared_edits/p/:id", () =>
|
|
helper.response({
|
|
state: "",
|
|
raw: "the latest iteration of the post",
|
|
version: 2,
|
|
message_bus_last_id: 0,
|
|
})
|
|
);
|
|
|
|
server.put("/shared_edits/p/:id", () => helper.response({ success: "OK" }));
|
|
|
|
server.put("/shared_edits/p/:id/commit.json", () => {
|
|
commitCalls.push(Date.now());
|
|
return helper.response({ success: "OK" });
|
|
});
|
|
|
|
server.put("/shared_edits/p/:id/selection", () =>
|
|
helper.response({ success: "OK" })
|
|
);
|
|
});
|
|
|
|
async function openSharedEdit() {
|
|
await visit("/t/internationalization-localization/280");
|
|
await click(".show-more-actions");
|
|
await click(".show-post-admin-menu");
|
|
await click(".admin-toggle-shared-edits");
|
|
await click(".shared-edit");
|
|
}
|
|
|
|
test("edit the first post", async function (assert) {
|
|
await openSharedEdit();
|
|
|
|
assert
|
|
.dom(".d-editor-input")
|
|
.hasValue(
|
|
"the latest iteration of the post",
|
|
"populates the input with the post text"
|
|
);
|
|
|
|
await click(".leave-shared-edit .btn-primary");
|
|
});
|
|
|
|
test("Done button commits and closes composer", async function (assert) {
|
|
commitCalls.length = 0;
|
|
|
|
await openSharedEdit();
|
|
|
|
assert.dom("#reply-control.open").exists("Composer is open");
|
|
|
|
await click(".leave-shared-edit .btn-primary");
|
|
|
|
// Wait for commit to complete
|
|
await waitUntil(() => commitCalls.length > 0, { timeout: 2000 });
|
|
|
|
assert.strictEqual(commitCalls.length, 1, "Commit was called once");
|
|
assert
|
|
.dom("#reply-control.open")
|
|
.doesNotExist("Composer is closed after Done");
|
|
});
|
|
|
|
test("creatingSharedEdit flag is set correctly", async function (assert) {
|
|
await openSharedEdit();
|
|
|
|
const composer = this.container.lookup("service:composer");
|
|
|
|
assert.true(
|
|
composer.model?.creatingSharedEdit,
|
|
"creatingSharedEdit is true when in shared edit mode"
|
|
);
|
|
|
|
await click(".leave-shared-edit .btn-primary");
|
|
});
|
|
|
|
test("composer shows Done button in shared edit mode", async function (assert) {
|
|
await openSharedEdit();
|
|
|
|
assert
|
|
.dom(".leave-shared-edit .btn-primary")
|
|
.exists("Done button is visible");
|
|
assert
|
|
.dom(".leave-shared-edit .btn-primary")
|
|
.hasText(/Done/i, "Button says Done");
|
|
|
|
await click(".leave-shared-edit .btn-primary");
|
|
});
|
|
|
|
test("editor mode toggle is hidden in shared edit mode", async function (assert) {
|
|
await openSharedEdit();
|
|
|
|
assert
|
|
.dom(".composer-toggle-switch")
|
|
.doesNotExist("editor mode toggle is hidden when in shared edit mode");
|
|
|
|
await click(".leave-shared-edit .btn-primary");
|
|
});
|
|
});
|