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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "notu",
3
- "version": "0.1.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
- Note
12
+ Attr,
13
+ Environment,
14
+ HttpClient,
15
+ Note,
16
+ NoteAttr,
17
+ NoteTag,
18
+ parseQuery,
19
+ Space,
20
+ Tag
5
21
  };
@@ -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
  });
@@ -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
  }
@@ -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
  });
@@ -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
  }