Skip to content

Commit

Permalink
fix: load unsave handlers only after editors mount (#351)
Browse files Browse the repository at this point in the history
  • Loading branch information
erickzhao authored Mar 12, 2020
1 parent 84ac41f commit 4ec5bec
Show file tree
Hide file tree
Showing 6 changed files with 37 additions and 55 deletions.
22 changes: 0 additions & 22 deletions src/renderer/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ export class App {

// once loaded, we have a "saved" state
this.state.isUnsaved = false;
this.setupUnsavedOnChangeListener();

return true;
}
Expand Down Expand Up @@ -177,30 +176,9 @@ export class App {

ipcRenderer.send(WEBCONTENTS_READY_FOR_IPC_SIGNAL);

// TODO: A timer here is terrible. Let's fix this
// and ensure we actually do it once Editors have mounted.
setTimeout(() => {
this.setupUnsavedOnChangeListener();
}, 1500);

return rendered;
}

/**
* If the editor is changed for the first time, we'll
* set `isUnsaved` to true. That way, the app can warn you
* if you're about to throw things away.
*/
public setupUnsavedOnChangeListener() {
Object.keys(window.ElectronFiddle.editors).forEach((key) => {
const editor = window.ElectronFiddle.editors[key];
const disposable = editor.onDidChangeModelContent(() => {
this.state.isUnsaved = true;
disposable.dispose();
});
});
}

/**
* Loads theme CSS into the HTML document.
*
Expand Down
10 changes: 5 additions & 5 deletions src/renderer/components/editors.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,14 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
this.setFocused = this.setFocused.bind(this);

this.state = { monacoOptions: defaultMonacoOptions };

this.loadMonaco();
}

/**
* Executed right after the component mounts. We'll setup the IPC listeners here.
*
* @memberof Editors
*/
public componentDidMount() {
public async componentDidMount() {
ipcRendererManager.on(IpcEvents.MONACO_EXECUTE_COMMAND, (_event, cmd: string) => {
this.executeCommand(cmd);
});
Expand All @@ -95,6 +93,8 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
});

this.setState({ isMounted: true });
await this.loadMonaco();
this.props.appState.isUnsaved = false;
}

public componentWillUnmount() {
Expand Down Expand Up @@ -291,7 +291,7 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
* We're doing things a bit roundabout to ensure that we're not overloading the
* mobx state with a gigantic Monaco tree.
*/
public async loadMonaco(): Promise<void> {
public async loadMonaco() {
const { app } = window.ElectronFiddle;
const loader = require('monaco-loader');
const monaco = app.monaco || await loader();
Expand All @@ -309,7 +309,7 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
this.setState({ monaco });
}

activateTheme(monaco, undefined, this.props.appState.theme);
await activateTheme(monaco, undefined, this.props.appState.theme);
}

/**
Expand Down
1 change: 0 additions & 1 deletion src/renderer/file-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@ export class FileManager {
}

this.appState.isUnsaved = false;
window.ElectronFiddle.app.setupUnsavedOnChangeListener();
}
}

Expand Down
15 changes: 13 additions & 2 deletions src/renderer/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export class AppState {

@observable public isPublishing: boolean = false;
@observable public isRunning: boolean = false;
@observable public isUnsaved: boolean = false;
@observable public isUnsaved: boolean;
@observable public isUpdatingElectronVersions: boolean = false;

// -- Various "isShowing" settings ------------------
Expand Down Expand Up @@ -167,7 +167,9 @@ export class AppState {
autorun(() => this.save('statesToShow', this.statesToShow));

autorun(() => {
if (this.isUnsaved) {
if (typeof this.isUnsaved === 'undefined') return;

if (!!this.isUnsaved) {
window.onbeforeunload = () => {
ipcRendererManager.send(IpcEvents.SHOW_INACTIVE);
this.setGenericDialogOptions({
Expand Down Expand Up @@ -200,6 +202,15 @@ export class AppState {
};
} else {
window.onbeforeunload = null;

// set up editor listeners to verify if unsaved
Object.keys(window.ElectronFiddle.editors).forEach((key) => {
const editor = window.ElectronFiddle.editors[key];
const disposable = editor.onDidChangeModelContent(() => {
this.isUnsaved = true;
disposable.dispose();
});
});
}
});

Expand Down
21 changes: 0 additions & 21 deletions tests/renderer/app-spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,9 @@ describe('Editors component', () => {

const app = new App();
const result = (await app.setup()) as HTMLDivElement;
app.setupUnsavedOnChangeListener = jest.fn();
jest.runAllTimers();

expect(result.innerHTML).toBe('Dialogs;Header;OutputEditorsWrapper;');
expect(app.setupUnsavedOnChangeListener).toHaveBeenCalled();

jest.useRealTimers();
});
Expand Down Expand Up @@ -174,7 +172,6 @@ describe('Editors component', () => {
const app = new App();
(app.state as Partial<AppState>) = new MockState();
app.state.isUnsaved = false;
app.setupUnsavedOnChangeListener = jest.fn();
app.setEditorValues = jest.fn();

const editorValues = {
Expand All @@ -190,7 +187,6 @@ describe('Editors component', () => {
})
.then(() => {
expect(app.state.isUnsaved).toBe(false);
expect(app.setupUnsavedOnChangeListener).toHaveBeenCalled();
done();
});
});
Expand Down Expand Up @@ -336,23 +332,6 @@ describe('Editors component', () => {
});
});

describe('setupUnsavedOnChangeListener()', () => {
it('listens for model change events', async () => {
const app = new App();

app.setupUnsavedOnChangeListener();

const fn = window.ElectronFiddle.editors!.renderer!
.onDidChangeModelContent;
const call = (fn as jest.Mock<any>).mock.calls[0];
const cb = call[0];

cb();

expect(app.state.isUnsaved).toBe(true);
});
});

describe('setupResizeListener()', () => {
it('attaches to the handler', () => {
window.addEventListener = jest.fn();
Expand Down
23 changes: 19 additions & 4 deletions tests/renderer/state-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ describe('AppState', () => {
expect(appState).toBeTruthy();
});

describe('onbeforeunload handler', () => {
it('closes the window', (done) => {
describe('isUnsaved autorun handler', () => {
it('can close the window if user accepts the dialog', (done) => {
window.close = jest.fn();
appState.isUnsaved = true;
expect(window.onbeforeunload).toBeTruthy();
Expand All @@ -73,7 +73,7 @@ describe('AppState', () => {
});
});

it('closes the app', (done) => {
it('can close the app after user accepts dialog', (done) => {
const { remote } = require('electron');
window.close = jest.fn();
appState.isUnsaved = true;
Expand All @@ -93,7 +93,7 @@ describe('AppState', () => {
});
});

it('does not close the window', (done) => {
it('takes no action if user cancels the dialog', (done) => {
window.close = jest.fn();
appState.isUnsaved = true;
expect(window.onbeforeunload).toBeTruthy();
Expand All @@ -109,6 +109,21 @@ describe('AppState', () => {
done();
});
});

it('sets the onDidChangeModelContent handler if saved', () => {
appState.isUnsaved = false;

expect(window.onbeforeunload).toBe(null);

const fn = window.ElectronFiddle.editors!.renderer!
.onDidChangeModelContent;
const call = (fn as jest.Mock<any>).mock.calls[0];
const cb = call[0];

cb();

expect(appState.isUnsaved).toBe(true);
});
});

describe('updateElectronVersions()', () => {
Expand Down

0 comments on commit 4ec5bec

Please sign in to comment.