notu 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -1
- package/src/Environment.test.ts +76 -0
- package/src/Environment.ts +51 -0
- package/src/index.ts +17 -1
- package/src/models/Attr.test.ts +21 -0
- package/src/models/Attr.ts +14 -0
- package/src/models/Note.test.ts +85 -0
- package/src/models/Note.ts +40 -0
- package/src/models/NoteAttr.test.ts +358 -0
- package/src/models/NoteAttr.ts +113 -0
- package/src/models/NoteTag.test.ts +45 -0
- package/src/models/NoteTag.ts +15 -0
- package/src/services/HttpClient.test.ts +108 -0
- package/src/services/HttpClient.ts +151 -0
- package/src/services/QueryParser.test.ts +153 -0
- package/src/services/QueryParser.ts +137 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "notu",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -17,5 +17,8 @@
|
|
|
17
17
|
"typescript": "^5.0.2",
|
|
18
18
|
"vite": "^4.4.5",
|
|
19
19
|
"vitest": "^0.34.4"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"axios": "^1.5.1"
|
|
20
23
|
}
|
|
21
24
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { expect, test } from 'vitest';
|
|
2
|
+
import Environment from './Environment';
|
|
3
|
+
import { NotuClient, NotuLoginResult } from './services/HttpClient';
|
|
4
|
+
import Space from './models/Space';
|
|
5
|
+
import { Note } from '.';
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TestClient implements NotuClient {
|
|
9
|
+
login(username: string, password: string): Promise<NotuLoginResult> {
|
|
10
|
+
return Promise.resolve({ success: true, error: null, token: 'qwer.asdf.zxcv' });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
getSpaces(): Promise<Array<Space>> {
|
|
14
|
+
const output = [
|
|
15
|
+
new Space('Space 1'),
|
|
16
|
+
new Space('Space 2')
|
|
17
|
+
];
|
|
18
|
+
output[0].id = 1;
|
|
19
|
+
output[1].id = 2;
|
|
20
|
+
for (const space of output)
|
|
21
|
+
space.clean();
|
|
22
|
+
return Promise.resolve(output);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
saveSpace(space: Space): Promise<Space> {
|
|
26
|
+
return Promise.resolve(space.duplicate());
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
getNotes(query: string, spaceId: number): Promise<Array<Note>> {
|
|
30
|
+
const output = [
|
|
31
|
+
new Note(),
|
|
32
|
+
new Note(),
|
|
33
|
+
new Note()
|
|
34
|
+
];
|
|
35
|
+
for (let i = 0; i < output.length; i++) {
|
|
36
|
+
output[i].text = 'Note ' + (i+1);
|
|
37
|
+
output[i].id = i+1;
|
|
38
|
+
output[i].spaceId = (i % 2) + 1;
|
|
39
|
+
output[i].clean();
|
|
40
|
+
}
|
|
41
|
+
return Promise.resolve(output);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
getNoteCount(query: string, spaceId: number): Promise<number> {
|
|
45
|
+
return Promise.resolve(3);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
saveNotes(notes: Array<Note>): Promise<Array<Note>> {
|
|
49
|
+
return Promise.resolve(notes.map(n => n.duplicate()));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
customJob(name: string, data: any): Promise<any> {
|
|
53
|
+
return Promise.resolve({ test: 123 });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
test('constructor takes client parameter', () => {
|
|
59
|
+
const client = new TestClient();
|
|
60
|
+
const env = new Environment(client);
|
|
61
|
+
|
|
62
|
+
expect(env.client).toBe(client);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('constructor throws error if client not set', () => {
|
|
66
|
+
expect(() => new Environment(null)).toThrowError();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
test('loadSpaces stores the list of spaces on the environment', async () => {
|
|
71
|
+
const env = new Environment(new TestClient());
|
|
72
|
+
|
|
73
|
+
await env.loadSpaces();
|
|
74
|
+
|
|
75
|
+
expect(env.spaces.length).toBe(2);
|
|
76
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { Note } from '.';
|
|
4
|
+
import Space from './models/Space';
|
|
5
|
+
import { NotuClient } from './services/HttpClient';
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export default class Environment {
|
|
9
|
+
|
|
10
|
+
private _client: NotuClient = null;
|
|
11
|
+
get client(): NotuClient { return this._client; }
|
|
12
|
+
|
|
13
|
+
private _spaces: Array<Space> = [];
|
|
14
|
+
get spaces(): Array<Space> { return this._spaces; }
|
|
15
|
+
|
|
16
|
+
constructor(client: NotuClient) {
|
|
17
|
+
if (!client)
|
|
18
|
+
throw Error('Client must be set on Environment constructor');
|
|
19
|
+
this._client = client;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async loadSpaces(): Promise<Array<Space>> {
|
|
23
|
+
this._spaces = await this.client.getSpaces();
|
|
24
|
+
return this.spaces;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async saveSpace(space: Space): Promise<Space> {
|
|
28
|
+
return await this.client.saveSpace(space);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async getNotes(query: string, spaceId: number): Promise<Array<Note>> {
|
|
32
|
+
return await this.client.getNotes(query, spaceId);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async getNoteCount(query: string, spaceId: number): Promise<number> {
|
|
36
|
+
return await this.client.getNoteCount(query, spaceId);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async saveNote(note: Note): Promise<Note> {
|
|
40
|
+
return (await this.client.saveNotes([note]))[0];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async saveNotes(notes: Array<Note>): Promise<Array<Note>> {
|
|
44
|
+
return await this.client.saveNotes(notes);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async customJob(name: string, data: any): Promise<any> {
|
|
48
|
+
return await this.client.customJob(name, data);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
|
+
import Attr from './models/Attr';
|
|
2
|
+
import Environment from './Environment';
|
|
3
|
+
import HttpClient from './services/HttpClient';
|
|
1
4
|
import Note from './models/Note';
|
|
5
|
+
import NoteAttr from './models/NoteAttr';
|
|
6
|
+
import NoteTag from './models/NoteTag';
|
|
7
|
+
import parseQuery from './services/QueryParser';
|
|
8
|
+
import Space from './models/Space';
|
|
9
|
+
import Tag from './models/Tag';
|
|
2
10
|
|
|
3
11
|
export {
|
|
4
|
-
|
|
12
|
+
Attr,
|
|
13
|
+
Environment,
|
|
14
|
+
HttpClient,
|
|
15
|
+
Note,
|
|
16
|
+
NoteAttr,
|
|
17
|
+
NoteTag,
|
|
18
|
+
parseQuery,
|
|
19
|
+
Space,
|
|
20
|
+
Tag
|
|
5
21
|
};
|
package/src/models/Attr.test.ts
CHANGED
|
@@ -190,4 +190,25 @@ test('validate throws error if arg set to true', () => {
|
|
|
190
190
|
const model = new Attr();
|
|
191
191
|
model.spaceId = 0;
|
|
192
192
|
expect(() => model.validate(true)).toThrowError();
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
test('defaultValue returns correct value for text', () => {
|
|
197
|
+
const model = new Attr().asText();
|
|
198
|
+
expect(model.defaultValue).toBe('');
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test('defaultValue returns correct value for number', () => {
|
|
202
|
+
const model = new Attr().asNumber();
|
|
203
|
+
expect(model.defaultValue).toBe(0);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test('defaultValue returns correct value for boolean', () => {
|
|
207
|
+
const model = new Attr().asBoolean();
|
|
208
|
+
expect(model.defaultValue).toBe(false);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test('defaultValue returns correct value for date', () => {
|
|
212
|
+
const model = new Attr().asDate();
|
|
213
|
+
expect(model.defaultValue.getSeconds()).toBe(new Date().getSeconds());
|
|
193
214
|
});
|
package/src/models/Attr.ts
CHANGED
|
@@ -108,4 +108,18 @@ export default class Attr extends ModelWithState<Attr> {
|
|
|
108
108
|
|
|
109
109
|
return output == null;
|
|
110
110
|
}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
get defaultValue(): any {
|
|
114
|
+
switch (this.type) {
|
|
115
|
+
case 'TEXT':
|
|
116
|
+
return '';
|
|
117
|
+
case 'NUMBER':
|
|
118
|
+
return 0;
|
|
119
|
+
case 'BOOLEAN':
|
|
120
|
+
return false;
|
|
121
|
+
case 'DATE':
|
|
122
|
+
return new Date();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
111
125
|
}
|
package/src/models/Note.test.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { expect, test } from 'vitest';
|
|
|
2
2
|
import Note from './Note';
|
|
3
3
|
import Tag from './Tag';
|
|
4
4
|
import Space from './Space';
|
|
5
|
+
import Attr from './Attr';
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
function newCleanTag(): Tag {
|
|
@@ -11,6 +12,13 @@ function newCleanTag(): Tag {
|
|
|
11
12
|
return tag;
|
|
12
13
|
}
|
|
13
14
|
|
|
15
|
+
function newCleanAttr(): Attr {
|
|
16
|
+
const attr = new Attr();
|
|
17
|
+
attr.id = 234;
|
|
18
|
+
attr.clean();
|
|
19
|
+
return attr;
|
|
20
|
+
}
|
|
21
|
+
|
|
14
22
|
|
|
15
23
|
test('gets initiated with sensible defaults', () => {
|
|
16
24
|
const note = new Note();
|
|
@@ -245,6 +253,15 @@ test('validate calls validate on each added tag', () => {
|
|
|
245
253
|
expect(note.validate()).toBe(false);
|
|
246
254
|
});
|
|
247
255
|
|
|
256
|
+
test('validate calls validate on each added attr', () => {
|
|
257
|
+
const note = new Note();
|
|
258
|
+
note.spaceId = 123;
|
|
259
|
+
const na = note.addAttr(newCleanAttr());
|
|
260
|
+
expect(note.validate()).toBe(true);
|
|
261
|
+
na.attrId = 0;
|
|
262
|
+
expect(note.validate()).toBe(false);
|
|
263
|
+
});
|
|
264
|
+
|
|
248
265
|
|
|
249
266
|
test('addTag adds new NoteTag object', () => {
|
|
250
267
|
const tag = newCleanTag();
|
|
@@ -319,4 +336,72 @@ test('removeTag marks existing tag on note as deleted', () => {
|
|
|
319
336
|
|
|
320
337
|
expect(note.tags.length).toBe(1);
|
|
321
338
|
expect(note.tags[0].isDeleted).toBe(true);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
test('addAttr adds new NoteAttr object', () => {
|
|
343
|
+
const attr = newCleanAttr();
|
|
344
|
+
const note = new Note();
|
|
345
|
+
|
|
346
|
+
note.addAttr(attr);
|
|
347
|
+
|
|
348
|
+
expect(note.attrs.length).toBe(1);
|
|
349
|
+
expect(note.attrs[0].note).toBe(note);
|
|
350
|
+
expect(note.attrs[0].attr).toBe(attr);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test('addAttr returns existing NoteAttr object if trying to add duplicate attr', () => {
|
|
354
|
+
const attr = newCleanAttr();
|
|
355
|
+
const note = new Note();
|
|
356
|
+
note.addAttr(attr);
|
|
357
|
+
|
|
358
|
+
note.addAttr(attr);
|
|
359
|
+
|
|
360
|
+
expect(note.attrs.length).toBe(1);
|
|
361
|
+
expect(note.attrs[0].note).toBe(note);
|
|
362
|
+
expect(note.attrs[0].attr).toBe(attr);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
test('addAttr undeletes existing NoteAttr if trying to add duplicate tag', () => {
|
|
366
|
+
const attr = newCleanAttr();
|
|
367
|
+
const note = new Note();
|
|
368
|
+
const na = note.addAttr(attr);
|
|
369
|
+
na.delete();
|
|
370
|
+
|
|
371
|
+
note.addAttr(attr);
|
|
372
|
+
|
|
373
|
+
expect(note.attrs.length).toBe(1);
|
|
374
|
+
expect(na.isDirty).toBe(true);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
test('addAttr throws error if trying to add deleted attr', () => {
|
|
378
|
+
const attr = newCleanAttr().delete();
|
|
379
|
+
const note = new Note();
|
|
380
|
+
expect(() => note.addAttr(attr)).toThrowError();
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
test('addAttr prevents note from adding attr that hasnt been saved yet', () => {
|
|
384
|
+
const note = new Note();
|
|
385
|
+
expect(() => note.addAttr(new Attr())).toThrowError();
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
test('removeAttr removes newly added attr from note', () => {
|
|
389
|
+
const attr = newCleanAttr();
|
|
390
|
+
const note = new Note();
|
|
391
|
+
note.addAttr(attr);
|
|
392
|
+
|
|
393
|
+
note.removeAttr(attr);
|
|
394
|
+
|
|
395
|
+
expect(note.attrs.length).toBe(0);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
test('removeAttr marks existing attr on note as deleted', () => {
|
|
399
|
+
const attr = newCleanAttr();
|
|
400
|
+
const note = new Note();
|
|
401
|
+
note.addAttr(attr).clean();
|
|
402
|
+
|
|
403
|
+
note.removeAttr(attr);
|
|
404
|
+
|
|
405
|
+
expect(note.attrs.length).toBe(1);
|
|
406
|
+
expect(note.attrs[0].isDeleted).toBe(true);
|
|
322
407
|
});
|
package/src/models/Note.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
import ModelWithState from './ModelWithState';
|
|
4
|
+
import NoteAttr from './NoteAttr';
|
|
4
5
|
import NoteTag from './NoteTag';
|
|
5
6
|
import Space from './Space';
|
|
6
7
|
import Tag from './Tag';
|
|
8
|
+
import Attr from './Attr';
|
|
7
9
|
|
|
8
10
|
|
|
9
11
|
export default class Note extends ModelWithState<Note> {
|
|
@@ -129,6 +131,40 @@ export default class Note extends ModelWithState<Note> {
|
|
|
129
131
|
}
|
|
130
132
|
|
|
131
133
|
|
|
134
|
+
private _attrs: Array<NoteAttr> = [];
|
|
135
|
+
get attrs(): Array<NoteAttr> { return this._attrs; }
|
|
136
|
+
|
|
137
|
+
addAttr(attr: Attr): NoteAttr {
|
|
138
|
+
if (attr.isDeleted)
|
|
139
|
+
throw Error('Cannot add an attribute marked as deleted to a note');
|
|
140
|
+
if (attr.isNew)
|
|
141
|
+
throw Error('Cannot add an attribute that hasn\'t yet been saved to a note');
|
|
142
|
+
let na = this.attrs.find(x => x.attrId == attr.id);
|
|
143
|
+
if (!!na) {
|
|
144
|
+
if (na.isDeleted)
|
|
145
|
+
na.dirty();
|
|
146
|
+
return na;
|
|
147
|
+
}
|
|
148
|
+
na = new NoteAttr();
|
|
149
|
+
na.note = this;
|
|
150
|
+
na.attr = attr;
|
|
151
|
+
this._attrs.push(na);
|
|
152
|
+
return na;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
removeAttr(attr: Attr): Note {
|
|
156
|
+
const na = this.attrs.find(x => x.attrId == attr.id);
|
|
157
|
+
if (!na)
|
|
158
|
+
return this;
|
|
159
|
+
|
|
160
|
+
if (na.isNew)
|
|
161
|
+
this._attrs = this._attrs.filter(x => x !== na);
|
|
162
|
+
else
|
|
163
|
+
na.delete();
|
|
164
|
+
return this;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
|
|
132
168
|
duplicate(): Note {
|
|
133
169
|
const output = new Note();
|
|
134
170
|
output.id = this.id;
|
|
@@ -158,6 +194,10 @@ export default class Note extends ModelWithState<Note> {
|
|
|
158
194
|
if (!nt.validate(throwError))
|
|
159
195
|
return false;
|
|
160
196
|
}
|
|
197
|
+
for (const na of this.attrs) {
|
|
198
|
+
if (!na.validate(throwError))
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
161
201
|
|
|
162
202
|
return output == null;
|
|
163
203
|
}
|