Skip to content

Commit 44bcfa1

Browse files
committed
test: add integration test for file recovery
1 parent ce3f818 commit 44bcfa1

File tree

5 files changed

+260
-14
lines changed

5 files changed

+260
-14
lines changed

src/extensions/default/NavigationAndHistory/FileRecovery.js

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ define(function (require, exports, module) {
6565
EventManager.registerEventHandler("ph-recovery", exports);
6666

6767
const BACKUP_INTERVAL_MS = 5000;
68-
const sessionRestoreDir = FileSystem.getDirectoryForPath(
68+
let sessionRestoreDir = FileSystem.getDirectoryForPath(
6969
path.normalize(NativeApp.getApplicationSupportDirectory() + "/sessionRestore"));
7070

7171
const trackedProjects = {};
@@ -376,19 +376,29 @@ define(function (require, exports, module) {
376376
}
377377
}
378378

379+
function initWith(scanIntervalMs, restoreDir) {
380+
ProjectManager.on(ProjectManager.EVENT_AFTER_PROJECT_OPEN, projectOpened);
381+
ProjectManager.on(ProjectManager.EVENT_PROJECT_BEFORE_CLOSE, beforeProjectClosed);
382+
exports.on("restoreProject", restoreBtnClicked);
383+
sessionRestoreDir = restoreDir;
384+
createDir(sessionRestoreDir);
385+
setInterval(changeScanner, scanIntervalMs);
386+
let currentProjectRoot = ProjectManager.getProjectRoot();
387+
if(currentProjectRoot) {
388+
// At boot, the startup project may be opened and we may never get the projectOpened event triggered
389+
// for the startup project. So we call manually.
390+
projectOpened(null, currentProjectRoot);
391+
}
392+
}
393+
379394
function init() {
380395
if(!window.testEnvironment){
381-
ProjectManager.on(ProjectManager.EVENT_AFTER_PROJECT_OPEN, projectOpened);
382-
ProjectManager.on(ProjectManager.EVENT_PROJECT_BEFORE_CLOSE, beforeProjectClosed);
383-
exports.on("restoreProject", restoreBtnClicked);
384-
createDir(sessionRestoreDir);
385-
setInterval(changeScanner, BACKUP_INTERVAL_MS);
386-
let currentProjectRoot = ProjectManager.getProjectRoot();
387-
if(currentProjectRoot) {
388-
// At boot, the startup project may be opened and we may never get the projectOpened event triggered
389-
// for the startup project. So we call manually.
390-
projectOpened(null, currentProjectRoot);
391-
}
396+
initWith(BACKUP_INTERVAL_MS, sessionRestoreDir);
397+
} else {
398+
// this is a test environment, expose functions to test
399+
exports.getProjectRestoreRoot = getProjectRestoreRoot;
400+
exports.initWith = initWith;
401+
window._FileRecoveryExtensionForTests = exports;
392402
}
393403
}
394404

src/extensions/default/NavigationAndHistory/html/recovery-template.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<br />
44
<button
55
style="float: right; margin-right: 6px"
6-
class="btn btn-mini remove primary"
6+
class="btn btn-mini remove primary file-recovery-button"
77
data-project="{{PROJECT_TO_RECOVER}}"
88
onclick="EventManager.triggerEvent('ph-recovery', 'restoreProject', this.getAttribute('data-project'))"
99
>
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
/*
2+
* GNU AGPL-3.0 License
3+
*
4+
* Copyright (c) 2021 - present core.ai . All rights reserved.
5+
* Original work Copyright (c) 2012 - 2021 Adobe Systems Incorporated. All rights reserved.
6+
*
7+
* This program is free software: you can redistribute it and/or modify it
8+
* under the terms of the GNU Affero General Public License as published by
9+
* the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful, but WITHOUT
13+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
15+
* for more details.
16+
*
17+
* You should have received a copy of the GNU Affero General Public License
18+
* along with this program. If not, see https://opensource.org/licenses/AGPL-3.0.
19+
*
20+
*/
21+
22+
/*global describe, it, expect, beforeAll, afterAll, awaitsForDone, beforeEach, awaits, awaitsFor */
23+
24+
define(function (require, exports, module) {
25+
// Recommended to avoid reloading the integration test window Phoenix instance for each test.
26+
27+
const SpecRunnerUtils = brackets.getModule("spec/SpecRunnerUtils");
28+
29+
describe("integration:FileRecovery integration tests", function () {
30+
31+
const testPathOriginal = SpecRunnerUtils.getTestPath("/spec/ProjectManager-test-files");
32+
const testPath = SpecRunnerUtils.getTestRoot() + "/navigationTests/";
33+
const tempRestorePath = SpecRunnerUtils.getTestRoot() + "/navigationTestsRestore/";
34+
35+
let FileViewController, // loaded from brackets.test
36+
ProjectManager, // loaded from brackets.test;
37+
CommandManager,
38+
Commands,
39+
testWindow,
40+
EditorManager,
41+
MainViewManager,
42+
brackets,
43+
FileSystem,
44+
$;
45+
46+
47+
async function deletePath(pathToDel) {
48+
if(!pathToDel.startsWith("/")) {
49+
pathToDel = testPath + pathToDel;
50+
}
51+
let promise = SpecRunnerUtils.deletePath(pathToDel, true);
52+
await awaitsForDone(promise, "Remove " + pathToDel, 5000);
53+
}
54+
55+
async function loadTestWindow() {
56+
testWindow = await SpecRunnerUtils.createTestWindowAndRun();
57+
brackets = testWindow.brackets;
58+
$ = testWindow.$;
59+
FileViewController = brackets.test.FileViewController;
60+
ProjectManager = brackets.test.ProjectManager;
61+
CommandManager = brackets.test.CommandManager;
62+
Commands = brackets.test.Commands;
63+
EditorManager = brackets.test.EditorManager;
64+
MainViewManager = brackets.test.MainViewManager;
65+
FileSystem = brackets.test.FileSystem;
66+
await awaitsForDone(SpecRunnerUtils.copyPath(testPathOriginal, testPath), "copy temp files");
67+
await SpecRunnerUtils.loadProjectInTestWindow(testPath);
68+
}
69+
70+
beforeAll(async function () {
71+
await loadTestWindow();
72+
}, 30000);
73+
74+
afterAll(async function () {
75+
FileViewController = null;
76+
ProjectManager = null;
77+
testWindow = null;
78+
brackets = null;
79+
await deletePath(testPath);
80+
await deletePath(tempRestorePath);
81+
await SpecRunnerUtils.closeTestWindow();
82+
});
83+
84+
beforeEach(async function () {
85+
await deletePath(testPath);
86+
await deletePath(tempRestorePath);
87+
await awaitsForDone(SpecRunnerUtils.copyPath(testPathOriginal, testPath), "copy temp files");
88+
});
89+
90+
async function closeSession() {
91+
await awaitsForDone(CommandManager.execute(Commands.FILE_CLOSE_ALL, { _forceClose: true }),
92+
"closing all file");
93+
}
94+
95+
async function openFile(relativePath) {
96+
await awaitsForDone(
97+
FileViewController.openAndSelectDocument(
98+
testPath + relativePath,
99+
FileViewController.PROJECT_MANAGER
100+
));
101+
}
102+
103+
function isFileOpen(relativePath) {
104+
const fullPath = testPath + relativePath;
105+
let allOpenFiles = MainViewManager.getAllOpenFiles();
106+
for(let file of allOpenFiles){
107+
if(file.fullPath === fullPath){
108+
return true;
109+
}
110+
}
111+
return false;
112+
}
113+
114+
async function initFileRestorer(fileToOpen) {
115+
await openFile(fileToOpen);
116+
expect(isFileOpen(fileToOpen)).toBeTrue();
117+
expect(testWindow._FileRecoveryExtensionForTests).exists;
118+
expect(await SpecRunnerUtils.pathExists(tempRestorePath, true)).toBeFalse();
119+
testWindow._FileRecoveryExtensionForTests.initWith(100,
120+
FileSystem.getDirectoryForPath(tempRestorePath));
121+
await SpecRunnerUtils.waitTillPathExists(tempRestorePath);
122+
123+
}
124+
125+
it("Should create restore folders and backup files", async function () {
126+
await initFileRestorer("file.js");
127+
let projectRestorePath = testWindow._FileRecoveryExtensionForTests.getProjectRestoreRoot(testPath);
128+
129+
// now edit a file so that its backup is created
130+
let editor = EditorManager.getActiveEditor();
131+
editor.document.setText("hello");
132+
await SpecRunnerUtils.waitTillPathExists(projectRestorePath.fullPath, true);
133+
await SpecRunnerUtils.waitTillPathExists(projectRestorePath.fullPath + "file.js", false);
134+
await closeSession();
135+
});
136+
137+
it("Should saving files remove file restore folder", async function () {
138+
await initFileRestorer("toDelete1/file.js");
139+
let projectRestorePath = testWindow._FileRecoveryExtensionForTests.getProjectRestoreRoot(testPath);
140+
141+
// now edit a file so that its backup is created
142+
let editor = EditorManager.getActiveEditor();
143+
editor.document.setText("hello");
144+
await SpecRunnerUtils.waitTillPathExists(projectRestorePath.fullPath + "toDelete1/file.js", false);
145+
await awaitsForDone(CommandManager.execute(Commands.FILE_SAVE_ALL), "saving all file");
146+
await SpecRunnerUtils.waitTillPathNotExists(projectRestorePath.fullPath + "toDelete1/file.js", false);
147+
await closeSession();
148+
});
149+
150+
it("Should show restore notification and restore if there is anything to restore", async function () {
151+
await initFileRestorer("toDelete1/file.js");
152+
let projectRestorePath = testWindow._FileRecoveryExtensionForTests.getProjectRestoreRoot(testPath);
153+
154+
// now edit a file so that its backup is created
155+
const unsavedText = "hello" + Math.random();
156+
let editor = EditorManager.getActiveEditor();
157+
editor.document.setText(unsavedText);
158+
await SpecRunnerUtils.waitTillPathExists(projectRestorePath.fullPath + "toDelete1/file.js", false);
159+
160+
// backup is now present, reload the project
161+
await SpecRunnerUtils.closeTestWindow();
162+
await loadTestWindow();
163+
testWindow._FileRecoveryExtensionForTests.initWith(100,
164+
FileSystem.getDirectoryForPath(tempRestorePath));
165+
await awaitsFor(()=>{
166+
return $(".file-recovery-button").length === 1;
167+
}, "waiting for restore notification", 5000);
168+
169+
// now press the recover button to start the recovery
170+
$(".file-recovery-button").click();
171+
// check if the file is recovered
172+
await awaitsFor(()=>{
173+
editor = EditorManager.getActiveEditor();
174+
return editor && editor.document.getText() === unsavedText;
175+
}, "waiting for restore notification", 5000);
176+
await closeSession();
177+
}, 10000);
178+
});
179+
});
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* GNU AGPL-3.0 License
3+
*
4+
* Copyright (c) 2021 - present core.ai . All rights reserved.
5+
* Original work Copyright (c) 2012 - 2021 Adobe Systems Incorporated. All rights reserved.
6+
*
7+
* This program is free software: you can redistribute it and/or modify it
8+
* under the terms of the GNU Affero General Public License as published by
9+
* the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful, but WITHOUT
13+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
15+
* for more details.
16+
*
17+
* You should have received a copy of the GNU Affero General Public License
18+
* along with this program. If not, see https://opensource.org/licenses/AGPL-3.0.
19+
*
20+
*/
21+
22+
/*global describe, it, expect, beforeEach, afterEach */
23+
24+
define(function (require, exports, module) {
25+
require("integ-tests");
26+
});

test/spec/SpecRunnerUtils.js

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
*
2020
*/
2121

22-
/*global jsPromise, jasmine, expect, beforeEach, awaitsFor,awaitsForDone, spyOn, KeyboardEvent, waits */
22+
/*global jsPromise, jasmine, expect, beforeEach, awaitsFor,awaitsForDone, spyOn, KeyboardEvent, waits, awaits */
2323

2424
define(function (require, exports, module) {
2525

@@ -1203,6 +1203,34 @@ define(function (require, exports, module) {
12031203
return 0;
12041204
}
12051205

1206+
function pathExists(pathToCheck, isFolder = true) {
1207+
let entry = isFolder ? FileSystem.getDirectoryForPath(pathToCheck)
1208+
: FileSystem.getFileForPath(pathToCheck);
1209+
return entry.existsAsync(pathToCheck);
1210+
}
1211+
1212+
async function waitTillPathExists(pathToCheck, isFolder = true, timeout = 2000, pollingInterval = 10) {
1213+
for(let i=0; i<timeout/pollingInterval; i++){
1214+
let exists = await pathExists(pathToCheck, isFolder);
1215+
if(exists){
1216+
return true;
1217+
}
1218+
await awaits(pollingInterval);
1219+
}
1220+
throw "Path exist check failed: " + pathToCheck;
1221+
}
1222+
1223+
async function waitTillPathNotExists(pathToCheck, isFolder = true, timeout = 2000, pollingInterval = 10) {
1224+
for(let i=0; i<timeout/pollingInterval; i++){
1225+
let exists = await pathExists(pathToCheck, isFolder);
1226+
if(!exists){
1227+
return true;
1228+
}
1229+
await awaits(pollingInterval);
1230+
}
1231+
throw "Path not exist check failed: " + pathToCheck;
1232+
}
1233+
12061234
// "global" custom matchers
12071235
function editorHasCursorPosition(editor, line, ch, ignoreSelection) {
12081236
let selection = editor.getSelection();
@@ -1253,6 +1281,9 @@ define(function (require, exports, module) {
12531281
exports.copyFileEntry = copyFileEntry;
12541282
exports.copyPath = copyPath;
12551283
exports.deletePath = deletePath;
1284+
exports.pathExists = pathExists;
1285+
exports.waitTillPathExists = waitTillPathExists;
1286+
exports.waitTillPathNotExists = waitTillPathNotExists;
12561287
exports.getTestWindow = getTestWindow;
12571288
exports.simulateKeyEvent = simulateKeyEvent;
12581289
exports.setLoadExtensionsInTestWindow = setLoadExtensionsInTestWindow;

0 commit comments

Comments
 (0)