Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 7 additions & 19 deletions scripts/e2e-session-persistence.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,7 @@ test('e2e: reload chosen but browser denies permission still restores snapshot',
});
});

test('e2e: restores source fallback when no workspace handle is available', async () => {
test('e2e: ignores legacy source-only snapshots when no workspace handle is available', async () => {
const storage = createStorageArea();
const handleStore = createHandleStore();
let restoredSource = null;
Expand Down Expand Up @@ -522,8 +522,8 @@ test('e2e: restores source fallback when no workspace handle is available', asyn

await secondSession.restoreSession();

assert.equal(restoredSource, 'int main() { return 0; }\n');
assert.equal(dirtyState, false);
assert.equal(restoredSource, null);
assert.equal(dirtyState, true);
});

test('e2e: formats the installed extension version from either extension namespace', () => {
Expand All @@ -538,12 +538,11 @@ test('e2e: formats the installed extension version from either extension namespa
assert.equal(getExtensionVersionLabel({}), '');
});

test('e2e: default and restored no-workspace source use an unsaved C++ tab that saves to a real path', async () => {
test('e2e: default no-workspace source uses an untitled tab that cannot compile', async () => {
const originalDocument = global.document;
global.document = createFakeDocument();
try {
let editorValue = '';
let suggestedName = null;
initToolbar(
{ onmessage: null, postMessage() {} },
{
Expand All @@ -556,28 +555,17 @@ test('e2e: default and restored no-workspace source use an unsaved C++ tab that
{ setWorkspace: () => {}, clearTerminal: () => {} },
{
newFile: () => {},
saveFile: async (_content, name) => {
suggestedName = name;
return 'hello.cpp';
},
},
() => {}
);

resetToNewProject();

assert.deepEqual(getToolbarOpenTabPaths(), ['unsaved.cpp']);
assert.equal(getToolbarActiveTabPath(), 'unsaved.cpp');
assert.deepEqual(getToolbarOpenTabPaths(), ['untitled:default']);
assert.equal(getToolbarActiveTabPath(), 'untitled:default');
assert.equal(global.document.getElementById('tab-bar').children[0].children[0].textContent, 'unsaved file');

const payload = await assembleCompilePayload({});
assert.equal(payload.primarySourcePath, 'unsaved.cpp');

global.document.getElementById('btn-save').click();
await waitFor(() => getToolbarActiveTabPath() === 'hello.cpp', 'saved tab should use its real file path');
assert.equal(suggestedName, 'main.cpp');
assert.deepEqual(getToolbarOpenTabPaths(), ['hello.cpp']);
assert.equal(global.document.getElementById('tab-bar').children[0].children[0].textContent, 'hello.cpp');
await assert.rejects(() => assembleCompilePayload({}), /Open a folder or save a file/);
} finally {
global.document = originalDocument;
}
Expand Down
19 changes: 7 additions & 12 deletions scripts/e2e-session-restore-choice.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ test('e2e: start-new clears session + handle + live fs state and loads default',
assert.equal(remaining.browser_cpp_session, null, 'persisted session cleared');
});

test('e2e: after start-new, the next persist saves fresh state not the abandoned workspace', async () => {
test('e2e: after start-new, the next persist leaves the untitled state unpersisted', async () => {
const storage = createStorageArea();
const handleStore = createHandleStore();
const handle = {
Expand All @@ -210,7 +210,7 @@ test('e2e: after start-new, the next persist saves fresh state not the abandoned
});

// Shared live state: clearPersistedSession() must null the directory handle so
// a follow-up persist records the default source-only session.
// a follow-up persist leaves the default untitled session unpersisted.
const live = { directoryHandle: handle, source: 'int main() { /* fresh */ }\n' };
const persistence = createSessionPersistence({
fsAPI: {
Expand All @@ -235,9 +235,7 @@ test('e2e: after start-new, the next persist saves fresh state not the abandoned
await persistence.persistSession();

const saved = (await storage.get('browser_cpp_session')).browser_cpp_session;
assert.ok(saved, 'a session is persisted');
assert.equal(saved.source, 'int main() { /* fresh */ }\n');
assert.equal(saved.openTabPaths, undefined, 'no workspace tabs persisted');
assert.equal(saved, null, 'the untitled buffer is not persisted');
assert.equal(handleStore.current, null, 'no directory handle persisted');
});

Expand Down Expand Up @@ -294,7 +292,7 @@ test('e2e: snapshot-only session prompts before restoring (reload chosen)', asyn
assert.deepEqual(restored[0].tabContentByPath, { 'main.cpp': 'int main(){}\n' });
});

test('e2e: source-only session auto-restores without prompting', async () => {
test('e2e: untitled source is neither persisted nor restored', async () => {
const storage = createStorageArea();
const handleStore = createHandleStore();

Expand All @@ -309,9 +307,9 @@ test('e2e: source-only session auto-restores without prompting', async () => {
handleStore,
});
await first.persistSession();
assert.equal((await storage.get('browser_cpp_session')).browser_cpp_session, null);

let restoredSource = null;
let dirty = true;
let confirmCalls = 0;
const second = createSessionPersistence({
fsAPI: { getDirectoryHandle: () => null, openFolderFromHandle: async () => null },
Expand All @@ -321,9 +319,7 @@ test('e2e: source-only session auto-restores without prompting', async () => {
restoredSource = source;
},
},
markDirty: (value) => {
dirty = value;
},
markDirty: () => {},
getOpenTabPaths: () => [],
getActiveTabPath: () => null,
restoreWorkspace: async () => {},
Expand All @@ -338,8 +334,7 @@ test('e2e: source-only session auto-restores without prompting', async () => {
await second.restoreSession();

assert.equal(confirmCalls, 0, 'source-only sessions never prompt');
assert.equal(restoredSource, 'int main() { return 0; }\n');
assert.equal(dirty, false);
assert.equal(restoredSource, null);
});

test('e2e: granted permission up front restores without prompting', async () => {
Expand Down
1 change: 1 addition & 0 deletions scripts/e2e-terminal-git-removal.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ test('e2e: tab completion no longer suggests git', () => {

test('e2e: git commands fall back to command not found', () => {
const ctx = setupTerminalHarness();
setWorkspace({ name: 'project', entries: [], git: { isRepo: false, branch: null, remotes: [] } });

typeText('git status');
pressEnter();
Expand Down
9 changes: 7 additions & 2 deletions scripts/e2e-terminal-mkdir.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -131,11 +131,16 @@ test('e2e: mkdir reports filesystem errors using shell-style messages', async ()
assert.ok(ctx.writes.join('').includes("mkdir: cannot create directory 'src/include': No such file or directory"));
});

test('e2e: mkdir fails when no workspace folder is open', async () => {
test('e2e: every terminal command except help is gated before a workspace opens', async () => {
const ctx = setupTerminalHarness();

await __executeTerminalCommandForTesting('mkdir include');
await __executeTerminalCommandForTesting('ls');
await __executeTerminalCommandForTesting('g++ main.cpp');

assert.ok(ctx.writes.join('').includes('mkdir: no folder opened'));
assert.equal(
(ctx.writes.join('').match(/Open a folder or save a file before using terminal commands\./g) || []).length,
3
);
assert.deepEqual(ctx.mkdirCalls, []);
});
2 changes: 2 additions & 0 deletions scripts/e2e-workspace-file-tracking.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1173,6 +1173,7 @@ test('e2e: setWorker disconnects the old worker and binds messages to the replac

test('e2e: compile-and-run pending state is cleared when the worker is replaced', async () => {
const ctx = await setupToolbar();
await ctx.toolbar.restoreWorkspace({ name: 'p', entries: [{ path: 'main.cpp', kind: 'file' }] }, [], null);
ctx.document.getElementById('btn-compile-run').click();
await tick();
assert.equal(ctx.workerCalls[0].type, 'compile');
Expand Down Expand Up @@ -1214,6 +1215,7 @@ test('e2e: after worker replacement, stale run-result from the old worker cannot

test('e2e: compile actions after worker replacement post to the replacement worker', async () => {
const ctx = await setupToolbar();
await ctx.toolbar.restoreWorkspace({ name: 'p', entries: [{ path: 'main.cpp', kind: 'file' }] }, [], null);
const replacementCalls = [];
const replacementWorker = { postMessage(msg) { replacementCalls.push(msg); }, onmessage: null };

Expand Down
16 changes: 3 additions & 13 deletions src/ui/session-persistence.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -141,13 +141,10 @@ function createIndexedDBHandleStore() {

export function createSessionPersistence({
fsAPI,
editorAPI,
markDirty,
getOpenTabPaths,
getActiveTabPath,
getOpenTabsSnapshot = () => null,
restoreWorkspace,
restoreNoWorkspaceSource = (source) => editorAPI.setValue(source),
storage = getStorageArea(),
handleStore = createIndexedDBHandleStore(),
confirmReload = () => true,
Expand Down Expand Up @@ -261,10 +258,8 @@ export function createSessionPersistence({
return;
}

if (session.source) {
restoreNoWorkspaceSource(session.source);
markDirty(false);
}
// Untitled buffers are deliberately ephemeral. Ignore legacy source-only
// snapshots rather than restoring content that was never saved to a folder.
} catch (_) {
// Storage not available – first run or non-extension context
}
Expand Down Expand Up @@ -298,12 +293,7 @@ export function createSessionPersistence({
});
} else {
await handleStore.clear();
await storageSet(storage, {
[STORAGE_KEY]: {
source: editorAPI.getValue(),
savedAt: Date.now(),
},
});
await storageSet(storage, { [STORAGE_KEY]: null });
}
} catch (err) {
console.warn('Failed to persist browser.cpp session:', err);
Expand Down
6 changes: 6 additions & 0 deletions src/ui/terminal.js
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,12 @@ async function executeCommand(cmdLine) {

const [cmd, ...args] = parts;

if (!workspaceName && cmd !== 'help') {
term.write(`${C.red}Open a folder or save a file before using terminal commands.${C.reset}${CRLF}`);
writePrompt();
return;
}

switch (cmd) {
case 'g++':
case 'clang++':
Expand Down
Loading
Loading