Skip to content

Commit 2bc51d6

Browse files
committed
Allowing Restore for chapters with invalid USFM
I removed the requirement that a chapter have valid USFM for the history revert feature to work. This allows users to restore revisions to get themselves out of unsupported USFM situations. I also verified that restoring a revision with unsupported USFM properly removes the ability to edit.
1 parent e167732 commit 2bc51d6

File tree

4 files changed

+114
-9
lines changed

4 files changed

+114
-9
lines changed

src/SIL.XForge.Scripture/ClientApp/src/app/core/text-doc.service.spec.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,96 @@ describe('TextDocService', () => {
9393
});
9494
});
9595

96+
describe('canRestore', () => {
97+
it('should return false if the project is undefined', () => {
98+
const env = new TestEnvironment();
99+
100+
// SUT
101+
const actual: boolean = env.textDocService.canRestore(undefined, 1, 1);
102+
expect(actual).toBe(false);
103+
});
104+
105+
it('should return false if user does not have general edit right', () => {
106+
const env = new TestEnvironment();
107+
const project = createTestProjectProfile({
108+
editable: true,
109+
sync: { dataInSync: true },
110+
texts: [
111+
{ bookNum: 1, chapters: [{ number: 1, isValid: true, permissions: { user01: TextInfoPermission.Write } }] }
112+
],
113+
userRoles: { user01: SFProjectRole.ParatextObserver }
114+
});
115+
116+
// SUT
117+
const actual: boolean = env.textDocService.canRestore(project, 1, 1);
118+
expect(actual).toBe(false);
119+
});
120+
121+
it('should return false if user does not have chapter edit permission', () => {
122+
const env = new TestEnvironment();
123+
const project = createTestProjectProfile({
124+
editable: true,
125+
sync: { dataInSync: true },
126+
texts: [
127+
{ bookNum: 1, chapters: [{ number: 1, isValid: true, permissions: { user01: TextInfoPermission.Read } }] }
128+
],
129+
userRoles: { user01: SFProjectRole.ParatextAdministrator }
130+
});
131+
132+
// SUT
133+
const actual: boolean = env.textDocService.canRestore(project, 1, 1);
134+
expect(actual).toBe(false);
135+
});
136+
137+
it('should return false if data is not in sync', () => {
138+
const env = new TestEnvironment();
139+
const project = createTestProjectProfile({
140+
editable: true,
141+
sync: { dataInSync: false },
142+
texts: [
143+
{ bookNum: 1, chapters: [{ number: 1, isValid: true, permissions: { user01: TextInfoPermission.Write } }] }
144+
],
145+
userRoles: { user01: SFProjectRole.ParatextAdministrator }
146+
});
147+
148+
// SUT
149+
const actual: boolean = env.textDocService.canRestore(project, 1, 1);
150+
expect(actual).toBe(false);
151+
});
152+
153+
it('should return false if editing is disabled', () => {
154+
const env = new TestEnvironment();
155+
const project = createTestProjectProfile({
156+
editable: false,
157+
sync: { dataInSync: true },
158+
texts: [
159+
{ bookNum: 1, chapters: [{ number: 1, isValid: true, permissions: { user01: TextInfoPermission.Write } }] }
160+
],
161+
userRoles: { user01: SFProjectRole.ParatextAdministrator }
162+
});
163+
164+
// SUT
165+
const actual: boolean = env.textDocService.canRestore(project, 1, 1);
166+
expect(actual).toBe(false);
167+
});
168+
169+
it('should return true if all conditions are met', () => {
170+
const env = new TestEnvironment();
171+
const project = createTestProjectProfile({
172+
editable: true,
173+
sync: { dataInSync: true },
174+
texts: [
175+
{ bookNum: 1, chapters: [{ number: 1, isValid: true, permissions: { user01: TextInfoPermission.Write } }] }
176+
],
177+
userRoles: { user01: SFProjectRole.ParatextAdministrator }
178+
});
179+
180+
// SUT
181+
const actual: boolean = env.textDocService.canRestore(project, 1, 1);
182+
expect(actual).toBe(true);
183+
});
184+
});
185+
96186
describe('createTextDoc', () => {
97187
it('should throw error if text doc already exists', fakeAsync(() => {
98188
const env = new TestEnvironment();

src/SIL.XForge.Scripture/ClientApp/src/app/core/text-doc.service.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,26 @@ export class TextDocService {
5454
* @param {SFProjectProfile | undefined} project The project.
5555
* @param {number | undefined} bookNum The book number.
5656
* @param {number | undefined} chapterNum The chapter number.
57-
* @returns {boolean} A value indicating whether the chapter can be edited by the current user.
57+
* @returns {boolean} A value indicating whether the chapter can be edited by the current user.
5858
*/
5959
canEdit(project: SFProjectProfile | undefined, bookNum: number | undefined, chapterNum: number | undefined): boolean {
60+
return this.isUsfmValid(project, bookNum, chapterNum) && this.canRestore(project, bookNum, chapterNum);
61+
}
62+
63+
/**
64+
* Determines if the current user can restore a previous revision for the specified chapter.
65+
*
66+
* @param {SFProjectProfile | undefined} project The project.
67+
* @param {number | undefined} bookNum The book number.
68+
* @param {number | undefined} chapterNum The chapter number.
69+
* @returns {boolean} A value indicating whether the chapter can be edited via history restore by the current user.
70+
*/
71+
canRestore(
72+
project: SFProjectProfile | undefined,
73+
bookNum: number | undefined,
74+
chapterNum: number | undefined
75+
): boolean {
6076
return (
61-
this.isUsfmValid(project, bookNum, chapterNum) &&
6277
this.userHasGeneralEditRight(project) &&
6378
this.hasChapterEditPermission(project, bookNum, chapterNum) &&
6479
this.isDataInSync(project) &&

src/SIL.XForge.Scripture/ClientApp/src/app/translate/editor/editor-history/history-chooser/history-chooser.component.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,9 @@ describe('HistoryChooserComponent', () => {
139139
expect(env.component.selectedSnapshot).toBeUndefined();
140140
}));
141141

142-
it('should not display the revert history button if the user cannot edit', fakeAsync(() => {
142+
it('should not display the revert history button if the user cannot restore', fakeAsync(() => {
143143
const env = new TestEnvironment();
144-
when(mockedTextDocService.canEdit(anything(), 40, 1)).thenReturn(false);
144+
when(mockedTextDocService.canRestore(anything(), 40, 1)).thenReturn(false);
145145
env.triggerNgOnChanges();
146146
env.wait();
147147
expect(env.revertHistoryButton).toBeNull();
@@ -287,7 +287,7 @@ describe('HistoryChooserComponent', () => {
287287
when(mockedProjectService.getProfile('project01')).thenCall(() =>
288288
this.realtimeService.subscribe(SFProjectProfileDoc.COLLECTION, 'project01')
289289
);
290-
when(mockedTextDocService.canEdit(anything(), 40, 1)).thenReturn(true);
290+
when(mockedTextDocService.canRestore(anything(), 40, 1)).thenReturn(true);
291291
}
292292

293293
get historySelect(): HTMLElement {

src/SIL.XForge.Scripture/ClientApp/src/app/translate/editor/editor-history/history-chooser/history-chooser.component.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ import { Canon } from '@sillsdev/scripture';
55
import { Delta } from 'quill';
66
import { TextData } from 'realtime-server/lib/esm/scriptureforge/models/text-data';
77
import {
8-
BehaviorSubject,
9-
Observable,
10-
Subject,
118
asyncScheduler,
9+
BehaviorSubject,
1210
combineLatest,
1311
map,
12+
Observable,
1413
observeOn,
1514
startWith,
15+
Subject,
1616
tap
1717
} from 'rxjs';
1818
import { isNetworkError } from 'xforge-common/command.service';
@@ -76,7 +76,7 @@ export class HistoryChooserComponent implements AfterViewInit, OnChanges {
7676
get canRestoreSnapshot(): boolean {
7777
return (
7878
this.selectedSnapshot?.data.ops != null &&
79-
this.textDocService.canEdit(this.projectDoc?.data, this.bookNum, this.chapter)
79+
this.textDocService.canRestore(this.projectDoc?.data, this.bookNum, this.chapter)
8080
);
8181
}
8282

0 commit comments

Comments
 (0)