OXYGENHUB
# 196392053c9f oxygen-vscode / test / oxy.test.mjs
9.0 KiB · last changed Commit box: amend mode, previous messages, signing indicator, and a warning before committing conflict markers; 0.9.0 1 month ago source blame history raw
bed495a9 Andy McCall ↶ 1
// The process wrapper against the real oxy binary on a scratch repository.
2
// Runs with `npm test`; needs oxy on PATH, at ~/.local/bin/oxy, or in OXY_BIN.
3
4
import { test } from "node:test";
5
import assert from "node:assert/strict";
6
import { mkdtempSync, writeFileSync, mkdirSync } from "node:fs";
7
import { tmpdir } from "node:os";
8
import { join } from "node:path";
9
import { Oxy, findBinary } from "../out/oxy.mjs";
10
11
const binary = process.env.OXY_BIN ?? findBinary(undefined);
12
const oxy = new Oxy(binary);
13
14
test("root, status, cat, log and commit round trip", async () => {
15
  const dir = mkdtempSync(join(tmpdir(), "oxy-vscode-"));
16
  const home = join(dir, "home");
17
  mkdirSync(home);
18
  process.env.OXY_HOME = home;
19
  await oxy.check(dir, ["init", "repo"]);
20
  const root = join(dir, "repo");
21
  await oxy.check(root, ["identity", "create", "--name", "Test", "--email", "[email protected]"]);
22
23
  const version = await oxy.version();
24
  assert.equal(version.protocol, 1);
25
  assert.equal(await oxy.root(dir), null, "outside a repository");
26
  const info = await oxy.root(root);
27
  assert.equal(info?.bookmark, "main");
28
  assert.ok(info?.root.endsWith("repo"));
29
30
  writeFileSync(join(root, "a.txt"), "one\n");
31
  let status = await oxy.status(root);
32
  assert.deepEqual(status.paths, [{ path: "a.txt", status: "Added" }]);
33
  assert.equal(status.clean, false);
34
  await oxy.commit(root, "first");
35
  status = await oxy.status(root);
36
  assert.equal(status.clean, true);
37
38
  writeFileSync(join(root, "a.txt"), "one\ntwo\n");
39
  assert.equal((await oxy.cat(root, "a.txt")).toString(), "one\n", "the base version");
40
  const log = await oxy.log(root, 10);
41
  assert.equal(log.changes.filter((c) => !c.working_copy).length, 1);
42
  assert.equal(log.changes.find((c) => !c.working_copy)?.signature?.status, "unsigned");
43
  assert.ok((await oxy.prompt(root)).includes("main"));
44
  const bookmarks = await oxy.bookmarks(root);
45
  assert.deepEqual(bookmarks.map((b) => [b.name, b.current]), [["main", true]]);
46
  await assert.rejects(oxy.cat(root, "missing.txt"), /not in that state/);
35d56b58 Andy McCall ↶ 47
5779e5c7 Andy McCall ↶ 48
  // Amend folds the edit into the single head.
49
  await oxy.amend(root, "first, amended");
50
  assert.equal((await oxy.status(root)).clean, true);
51
  assert.ok((await oxy.log(root, 10)).changes.some((c) => c.message === "first, amended"));
52
53
  // Hunks: commit only b.txt's, leave a.txt's edit, then discard it.
54
  writeFileSync(join(root, "a.txt"), "one\ntwo\nthree\n");
35d56b58 Andy McCall ↶ 55
  writeFileSync(join(root, "b.txt"), "b\n");
56
  const hunks = await oxy.hunks(root);
57
  assert.ok(hunks.length >= 2, JSON.stringify(hunks));
58
  const bHunk = hunks.find((h) => h.path === "b.txt");
59
  await oxy.commitHunks(root, "b only", [bHunk.index]);
60
  status = await oxy.status(root);
61
  assert.deepEqual(status.paths.map((p) => p.path), ["a.txt"], "a.txt stays uncommitted");
5779e5c7 Andy McCall ↶ 62
  assert.equal((await oxy.cat(root, "b.txt")).toString(), "b\n", "committed with its content");
35d56b58 Andy McCall ↶ 63
  await oxy.discard(root, ["a.txt"]);
64
  assert.equal((await oxy.status(root)).clean, true);
5779e5c7 Andy McCall ↶ 65
66
  // The operation log, and undo of the last operation (the commit).
35d56b58 Andy McCall ↶ 67
  const ops = await oxy.opLog(root);
68
  assert.ok(ops[0].current && ops.length > 3);
69
  assert.match(await oxy.undo(root), /Undone/);
5779e5c7 Andy McCall ↶ 70
  status = await oxy.status(root);
71
  assert.ok(status.paths.some((p) => p.path === "b.txt"), "the commit is undone; b.txt is uncommitted work again");
bed495a9 Andy McCall ↶ 72
});
c8f95589 Andy McCall ↶ 73
74
test("bookmarks, remotes and sync against a local origin", async () => {
75
  const dir = mkdtempSync(join(tmpdir(), "oxy-vscode-sync-"));
76
  const home = join(dir, "home");
77
  mkdirSync(home);
78
  process.env.OXY_HOME = home;
79
  await oxy.check(dir, ["init", "origin"]);
80
  await oxy.clone(dir, join(dir, "origin"), join(dir, "work"));
81
  const root = join(dir, "work");
82
  writeFileSync(join(root, "a.txt"), "a\n");
83
  await oxy.commit(root, "a");
84
  const remotes = await oxy.remotes(root);
85
  assert.equal(remotes[0].name, "origin");
86
  let push = await oxy.push(root);
87
  assert.equal(push[0].ok, true);
88
  let bookmarks = await oxy.bookmarks(root);
89
  assert.deepEqual(bookmarks.map((b) => [b.name, b.current, b.ahead, b.behind]), [["main", true, 0, 0]]);
90
91
  await oxy.bookmarkCreate(root, "feature");
92
  await oxy.bookmarkSwitch(root, "feature");
93
  writeFileSync(join(root, "a.txt"), "a\nfeature\n");
94
  await oxy.commit(root, "feature work");
95
  bookmarks = await oxy.bookmarks(root);
96
  assert.equal(bookmarks.find((b) => b.current)?.name, "feature");
97
  await oxy.bookmarkSwitch(root, "main");
98
  assert.equal((await oxy.cat(root, "a.txt")).toString(), "a\n");
99
  writeFileSync(join(root, "b.txt"), "b\n");
100
  await assert.rejects(oxy.bookmarkSwitch(root, "feature"), /has changes/);
101
  await oxy.commit(root, "b on main");
102
  assert.match(await oxy.merge(root, "feature"), /Merged/);
103
  assert.equal((await oxy.cat(root, "a.txt")).toString(), "a\nfeature\n");
104
  push = await oxy.push(root, "origin", "feature");
105
  assert.equal(push[0].ok, true);
106
  push = await oxy.push(root);
107
  assert.equal(push[0].ok, true);
108
  const fetch = await oxy.fetch(root);
109
  assert.equal(fetch.remote, "origin");
110
  const pull = await oxy.pull(root);
111
  assert.equal(pull.changed, false);
112
  await oxy.bookmarkDelete(root, "feature");
113
  bookmarks = await oxy.bookmarks(root);
114
  assert.deepEqual(bookmarks.map((b) => b.name), ["main"]);
ab81294a Andy McCall ↶ 115
116
  // Blame, show, diff text, and file history.
117
  const blame = await oxy.blame(root, "a.txt");
118
  assert.deepEqual(blame.map((l) => l.line), [1, 2]);
119
  assert.notEqual(blame[0].hash, blame[1].hash, "two changes introduced the two lines");
120
  const show = await oxy.show(root, `#${blame[1].hash.slice(0, 12)}`);
121
  assert.equal(show.message, "feature work");
122
  assert.deepEqual(show.files.map((f) => f.path), ["a.txt"]);
123
  assert.match(await oxy.diffText(root, `#${blame[1].hash.slice(0, 12)}`), /\+feature/);
124
  const history = await oxy.log(root, 50, "a.txt");
125
  assert.equal(history.changes.filter((c) => !c.working_copy).length, 2);
d2c9a584 Andy McCall ↶ 126
127
  // Tags, workspaces, trust, bisect status, subrepos.
128
  await oxy.tagCreate(root, "v1", [], "first");
129
  const tags = await oxy.tags(root);
130
  assert.deepEqual(tags.map((t) => [t.name, t.message]), [["v1", "first"]]);
131
  const workspaces = await oxy.workspaces(root);
132
  assert.equal(workspaces.length, 1);
133
  assert.equal(workspaces[0].current, true);
134
  assert.equal(await oxy.policyShow(root), null, "no policy yet");
135
  const verify = await oxy.verify(root);
136
  assert.ok(verify.every((v) => v.status === "unsigned"));
137
  assert.equal(await oxy.bisectStatus(root), null);
138
  assert.deepEqual(await oxy.submodules(root), []);
139
  await oxy.bisectStart(root);
140
  assert.equal((await oxy.bisectStatus(root))?.state, "need-marks");
141
  await oxy.bisectReset(root);
c8f95589 Andy McCall ↶ 142
});
143
b4626290 Andy McCall ↶ 144
145
test("split, squash and rebase through the history actions, each undoable", async () => {
146
  const dir = mkdtempSync(join(tmpdir(), "oxy-vscode-hist-"));
147
  const home = join(dir, "home");
148
  mkdirSync(home);
149
  process.env.OXY_HOME = home;
150
  await oxy.check(dir, ["init", "repo"]);
151
  const root = join(dir, "repo");
152
  await oxy.check(root, ["identity", "create", "--name", "Test", "--email", "[email protected]"]);
153
  writeFileSync(join(root, "a.txt"), "a\n");
154
  await oxy.commit(root, "one");
155
  writeFileSync(join(root, "b.txt"), "b\n");
156
  writeFileSync(join(root, "c.txt"), "c\n");
157
  await oxy.commit(root, "two files");
158
  const find = async (m) => (await oxy.log(root, 50)).changes.find((c) => !c.working_copy && c.message.startsWith(m));
159
160
  // Split: b.txt becomes its own change, c.txt stays.
161
  const two = await find("two files");
162
  const out = await oxy.split(root, `#${two.hash.slice(0, 12)}`, ["b.txt"]);
163
  assert.match(out, /Split into/);
164
  const part = await find("two files (part 1");
165
  assert.ok(part, "the first part exists");
166
  assert.deepEqual((await oxy.show(root, `#${part.hash.slice(0, 12)}`)).files.map((f) => f.path), ["b.txt"]);
167
  await oxy.undo(root);
168
  assert.equal(await find("two files (part 1"), undefined, "undo reverses the split");
169
170
  // Squash: an edit of a.txt folds into "one", which keeps its ChangeId.
171
  writeFileSync(join(root, "a.txt"), "a\na2\n");
172
  await oxy.commit(root, "edit a");
173
  const one = await find("one");
174
  const edit = await find("edit a");
175
  assert.deepEqual(edit.deps, [one.hash]);
176
  assert.match(await oxy.squash(root, `#${edit.hash.slice(0, 12)}`, `#${one.hash.slice(0, 12)}`), /Squashed into/);
177
  const after = await find("one");
178
  assert.equal(after.change_id, one.change_id);
179
  assert.equal(await find("edit a"), undefined);
180
181
  // Rebase onto a state that already has the change is refused, with oxy's reason.
182
  await assert.rejects(oxy.rebase(root, `#${after.hash.slice(0, 12)}`, ["main"]), /already part of the target state/);
183
});
44bfd5ad Andy McCall ↶ 184
185
test("identity reports how commits are signed", async () => {
186
  const dir = mkdtempSync(join(tmpdir(), "oxy-vscode-id-"));
187
  const home = join(dir, "home");
188
  mkdirSync(home);
189
  process.env.OXY_HOME = home;
190
  await oxy.check(dir, ["init", "repo"]);
191
  const root = join(dir, "repo");
192
  assert.equal(await oxy.identity(root), null, "none yet");
193
  await oxy.check(root, ["identity", "create", "--name", "Test", "--email", "[email protected]"]);
194
  const id = await oxy.identity(root);
195
  assert.equal(id?.who, "Test <[email protected]>");
196
  assert.equal(id?.secret, true);
197
  assert.ok(id?.mode === "change" || id?.mode === "seal");
198
});