notu 0.1.0 → 0.2.1
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/dist/notu.js +2060 -0
- package/dist/notu.umd.cjs +5 -0
- package/package.json +6 -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/models/Tag.test.ts +62 -0
- package/src/models/Tag.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
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import { expect, test } from 'vitest';
|
|
2
|
+
import NoteAttr from './NoteAttr';
|
|
3
|
+
import Note from './Note';
|
|
4
|
+
import Attr from './Attr';
|
|
5
|
+
import Tag from './Tag';
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
test('Gets initiated as new', () => {
|
|
9
|
+
const na = new NoteAttr();
|
|
10
|
+
expect(na.isNew).toBe(true);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test('Gets initiated with sensible defaults', () => {
|
|
14
|
+
const na = new NoteAttr();
|
|
15
|
+
expect(na.noteId).toBe(0);
|
|
16
|
+
expect(na.attrId).toBe(0);
|
|
17
|
+
expect(na.value).toBe(null);
|
|
18
|
+
expect(na.tagId).toBe(null);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
test('Set noteId marks object as dirty if currently clean', () => {
|
|
23
|
+
const na = new NoteAttr().clean();
|
|
24
|
+
na.noteId = 123;
|
|
25
|
+
expect(na.isDirty).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('Set noteId doesnt change state if new', () => {
|
|
29
|
+
const na = new NoteAttr().new();
|
|
30
|
+
na.noteId = 123;
|
|
31
|
+
expect(na.isNew).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('Set noteId doesnt change state if value not different', () => {
|
|
35
|
+
const na = new NoteAttr().clean();
|
|
36
|
+
na.noteId = na.noteId;
|
|
37
|
+
expect(na.isClean).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('Setting note with id different than current noteId updates state', () => {
|
|
41
|
+
const na = new NoteAttr();
|
|
42
|
+
na.noteId = 123;
|
|
43
|
+
na.clean();
|
|
44
|
+
const note = new Note();
|
|
45
|
+
note.id = 234;
|
|
46
|
+
|
|
47
|
+
na.note = note;
|
|
48
|
+
|
|
49
|
+
expect(na.noteId).toBe(234);
|
|
50
|
+
expect(na.isDirty).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('Setting note with id same as current noteId preserves state', () => {
|
|
54
|
+
const na = new NoteAttr();
|
|
55
|
+
na.noteId = 27;
|
|
56
|
+
na.clean();
|
|
57
|
+
const note = new Note();
|
|
58
|
+
note.id = 27;
|
|
59
|
+
|
|
60
|
+
na.note = note;
|
|
61
|
+
|
|
62
|
+
expect(na.noteId).toBe(27);
|
|
63
|
+
expect(na.isClean).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('Setting noteId to new value removes note object', () => {
|
|
67
|
+
const na = new NoteAttr();
|
|
68
|
+
const note = new Note();
|
|
69
|
+
note.id = 80;
|
|
70
|
+
na.note = note;
|
|
71
|
+
|
|
72
|
+
na.noteId = 81;
|
|
73
|
+
|
|
74
|
+
expect(na.note).toBeNull();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('Setting noteId to same as current note id preserves it', () => {
|
|
78
|
+
const na = new NoteAttr();
|
|
79
|
+
const note = new Note();
|
|
80
|
+
note.id = 80;
|
|
81
|
+
na.note = note;
|
|
82
|
+
|
|
83
|
+
na.noteId = 80;
|
|
84
|
+
|
|
85
|
+
expect(na.note).not.toBeNull();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
test('Set attrId marks object as dirty if currently clean', () => {
|
|
90
|
+
const na = new NoteAttr().clean();
|
|
91
|
+
na.attrId = 123;
|
|
92
|
+
expect(na.isDirty).toBe(true);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('Set attrId doesnt change state if new', () => {
|
|
96
|
+
const na = new NoteAttr().new();
|
|
97
|
+
na.attrId = 123;
|
|
98
|
+
expect(na.isNew).toBe(true);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('Set attrId doesnt change state if value not different', () => {
|
|
102
|
+
const na = new NoteAttr().clean();
|
|
103
|
+
na.attrId = na.attrId;
|
|
104
|
+
expect(na.isClean).toBe(true);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('Setting attr with id different than current attrId updates state', () => {
|
|
108
|
+
const na = new NoteAttr();
|
|
109
|
+
na.attrId = 123;
|
|
110
|
+
na.clean();
|
|
111
|
+
const attr = new Attr();
|
|
112
|
+
attr.id = 234;
|
|
113
|
+
|
|
114
|
+
na.attr = attr;
|
|
115
|
+
|
|
116
|
+
expect(na.attrId).toBe(234);
|
|
117
|
+
expect(na.isDirty).toBe(true);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('Setting attr with id same as current attrId preserves state', () => {
|
|
121
|
+
const na = new NoteAttr();
|
|
122
|
+
na.attrId = 27;
|
|
123
|
+
na.value = '';
|
|
124
|
+
na.clean();
|
|
125
|
+
const attr = new Attr();
|
|
126
|
+
attr.id = 27;
|
|
127
|
+
|
|
128
|
+
na.attr = attr;
|
|
129
|
+
|
|
130
|
+
expect(na.attrId).toBe(27);
|
|
131
|
+
expect(na.isClean).toBe(true);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('Setting attrId to new value removes attr object', () => {
|
|
135
|
+
const na = new NoteAttr();
|
|
136
|
+
const attr = new Attr();
|
|
137
|
+
attr.id = 80;
|
|
138
|
+
na.attr = attr;
|
|
139
|
+
|
|
140
|
+
na.attrId = 81;
|
|
141
|
+
|
|
142
|
+
expect(na.attr).toBeNull();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('Setting attrId to same as current attr id preserves it', () => {
|
|
146
|
+
const na = new NoteAttr();
|
|
147
|
+
const attr = new Attr();
|
|
148
|
+
attr.id = 80;
|
|
149
|
+
na.attr = attr;
|
|
150
|
+
|
|
151
|
+
na.attrId = 80;
|
|
152
|
+
|
|
153
|
+
expect(na.attr).not.toBeNull();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test('Setting attr updates the default value', () => {
|
|
157
|
+
const na = new NoteAttr();
|
|
158
|
+
const attr1 = new Attr().asNumber();
|
|
159
|
+
attr1.id = 1;
|
|
160
|
+
|
|
161
|
+
na.attr = attr1;
|
|
162
|
+
|
|
163
|
+
expect(na.value).toBe(0);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test('Setting attr to null clears out value', () => {
|
|
167
|
+
const na = new NoteAttr();
|
|
168
|
+
const attr = new Attr().asNumber();
|
|
169
|
+
attr.id = 1;
|
|
170
|
+
na.attr = attr;
|
|
171
|
+
|
|
172
|
+
na.attr = null;
|
|
173
|
+
|
|
174
|
+
expect(na.value).toBe(null);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test('Setting attr to new value of same type doesnt update value', () => {
|
|
178
|
+
const na = new NoteAttr();
|
|
179
|
+
const attr1 = new Attr().asNumber();
|
|
180
|
+
const attr2 = new Attr().asNumber();
|
|
181
|
+
attr1.id = 1;
|
|
182
|
+
attr2.id = 2;
|
|
183
|
+
na.attr = attr1;
|
|
184
|
+
na.value = 123;
|
|
185
|
+
|
|
186
|
+
na.attr = attr2;
|
|
187
|
+
|
|
188
|
+
expect(na.value).toBe(123);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test('Setting attr to new value of different type does update value', () => {
|
|
192
|
+
const na = new NoteAttr();
|
|
193
|
+
const attr1 = new Attr().asNumber();
|
|
194
|
+
const attr2 = new Attr().asText();
|
|
195
|
+
attr1.id = 1;
|
|
196
|
+
attr2.id = 2;
|
|
197
|
+
na.attr = attr1;
|
|
198
|
+
na.value = 123;
|
|
199
|
+
|
|
200
|
+
na.attr = attr2;
|
|
201
|
+
|
|
202
|
+
expect(na.value).toBe('');
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
test('Set value marks object as dirty if currently clean', () => {
|
|
207
|
+
const na = new NoteAttr().clean();
|
|
208
|
+
na.value = 123;
|
|
209
|
+
expect(na.isDirty).toBe(true);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test('Set value doesnt change state if new', () => {
|
|
213
|
+
const na = new NoteAttr().new();
|
|
214
|
+
na.value = 123;
|
|
215
|
+
expect(na.isNew).toBe(true);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test('Set value doesnt change state if value not different', () => {
|
|
219
|
+
const na = new NoteAttr().clean();
|
|
220
|
+
na.value = na.value;
|
|
221
|
+
expect(na.isClean).toBe(true);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
test('Set tagId marks object as dirty if currently clean', () => {
|
|
226
|
+
const na = new NoteAttr().clean();
|
|
227
|
+
na.tagId = 123;
|
|
228
|
+
expect(na.isDirty).toBe(true);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test('Set tagId doesnt change state if new', () => {
|
|
232
|
+
const na = new NoteAttr().new();
|
|
233
|
+
na.tagId = 123;
|
|
234
|
+
expect(na.isNew).toBe(true);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test('Set tagId doesnt change state if value not different', () => {
|
|
238
|
+
const na = new NoteAttr().clean();
|
|
239
|
+
na.tagId = na.tagId;
|
|
240
|
+
expect(na.isClean).toBe(true);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test('Setting tag with id different than current tagId updates state', () => {
|
|
244
|
+
const na = new NoteAttr();
|
|
245
|
+
na.tagId = 123;
|
|
246
|
+
na.clean();
|
|
247
|
+
const tag = new Tag();
|
|
248
|
+
tag.id = 234;
|
|
249
|
+
|
|
250
|
+
na.tag = tag;
|
|
251
|
+
|
|
252
|
+
expect(na.tagId).toBe(234);
|
|
253
|
+
expect(na.isDirty).toBe(true);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test('Setting tag with id same as current tagId preserves state', () => {
|
|
257
|
+
const na = new NoteAttr();
|
|
258
|
+
na.tagId = 27;
|
|
259
|
+
na.clean();
|
|
260
|
+
const tag = new Tag();
|
|
261
|
+
tag.id = 27;
|
|
262
|
+
|
|
263
|
+
na.tag = tag;
|
|
264
|
+
|
|
265
|
+
expect(na.tagId).toBe(27);
|
|
266
|
+
expect(na.isClean).toBe(true);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test('Setting tagId to new value removes tag object', () => {
|
|
270
|
+
const na = new NoteAttr();
|
|
271
|
+
const tag = new Tag();
|
|
272
|
+
tag.id = 80;
|
|
273
|
+
na.tag = tag;
|
|
274
|
+
|
|
275
|
+
na.tagId = 81;
|
|
276
|
+
|
|
277
|
+
expect(na.tag).toBeNull();
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
test('Setting tagId to same as current tag id preserves it', () => {
|
|
281
|
+
const na = new NoteAttr();
|
|
282
|
+
const tag = new Tag();
|
|
283
|
+
tag.id = 80;
|
|
284
|
+
na.tag = tag;
|
|
285
|
+
|
|
286
|
+
na.tagId = 80;
|
|
287
|
+
|
|
288
|
+
expect(na.tag).not.toBeNull();
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
test('Setting tag to null also nulls tagId', () => {
|
|
292
|
+
const na = new NoteAttr();
|
|
293
|
+
const tag = new Tag();
|
|
294
|
+
tag.id = 80;
|
|
295
|
+
na.tag = tag;
|
|
296
|
+
expect(na.tagId).toBe(80);
|
|
297
|
+
|
|
298
|
+
na.tag = null;
|
|
299
|
+
|
|
300
|
+
expect(na.tagId).toBe(null);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
test('Can duplicate itself', () => {
|
|
305
|
+
const na = new NoteAttr();
|
|
306
|
+
const note = new Note();
|
|
307
|
+
note.id = 24;
|
|
308
|
+
const attr = new Attr();
|
|
309
|
+
attr.id = 25;
|
|
310
|
+
const tag = new Tag();
|
|
311
|
+
tag.id = 26;
|
|
312
|
+
na.note = note;
|
|
313
|
+
na.attr = attr;
|
|
314
|
+
na.tag = tag;
|
|
315
|
+
na.value = 'hello';
|
|
316
|
+
|
|
317
|
+
const copy = na.duplicate();
|
|
318
|
+
|
|
319
|
+
expect(copy.note).toBe(na.note);
|
|
320
|
+
expect(copy.noteId).toBe(na.noteId);
|
|
321
|
+
expect(copy.attr).toBe(na.attr);
|
|
322
|
+
expect(copy.attrId).toBe(na.attrId);
|
|
323
|
+
expect(copy.tag).toBe(na.tag);
|
|
324
|
+
expect(copy.tagId).toBe(na.tagId);
|
|
325
|
+
expect(copy.value).toBe(na.value);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
test('validate passes if noteId is 0 and state is new', () => {
|
|
330
|
+
const nt = new NoteAttr();
|
|
331
|
+
nt.attrId = 123;
|
|
332
|
+
expect(nt.validate()).toBe(true);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
test('validate fails if noteId is 0 and state is not new', () => {
|
|
336
|
+
const nt = new NoteAttr();
|
|
337
|
+
nt.attrId = 123;
|
|
338
|
+
nt.clean();
|
|
339
|
+
expect(nt.validate()).toBe(false);
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
test('validate fails if attrId is 0', () => {
|
|
343
|
+
const nt = new NoteAttr();
|
|
344
|
+
nt.noteId = 123;
|
|
345
|
+
expect(nt.validate()).toBe(false);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
test('validate throws error if arg set to true', () => {
|
|
349
|
+
const nt = new NoteAttr();
|
|
350
|
+
expect(() => nt.validate(true)).toThrowError();
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test('validate succeeds if noteId & attrId are both positive values', () => {
|
|
354
|
+
const nt = new NoteAttr();
|
|
355
|
+
nt.noteId = 123;
|
|
356
|
+
nt.attrId = 234;
|
|
357
|
+
expect(nt.validate()).toBe(true);
|
|
358
|
+
});
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import ModelWithState from './ModelWithState';
|
|
4
|
+
import Note from './Note';
|
|
5
|
+
import Attr from './Attr';
|
|
6
|
+
import Tag from './Tag';
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
export default class NoteAttr extends ModelWithState<NoteAttr> {
|
|
10
|
+
private _noteId: number = 0;
|
|
11
|
+
get noteId(): number { return this._noteId; }
|
|
12
|
+
set noteId(value: number) {
|
|
13
|
+
if (value !== this._noteId) {
|
|
14
|
+
this._noteId = value;
|
|
15
|
+
if (value !== this.note?.id ?? 0)
|
|
16
|
+
this._note = null;
|
|
17
|
+
if (this.isClean)
|
|
18
|
+
this.dirty();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private _note: Note = null;
|
|
23
|
+
get note(): Note { return this._note; }
|
|
24
|
+
set note(value: Note) {
|
|
25
|
+
this._note = value;
|
|
26
|
+
this.noteId = value?.id ?? 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
private _attrId: number = 0;
|
|
31
|
+
get attrId(): number { return this._attrId; }
|
|
32
|
+
set attrId(value: number) {
|
|
33
|
+
if (value !== this._attrId) {
|
|
34
|
+
this._attrId = value;
|
|
35
|
+
if (value !== this.attr?.id ?? 0)
|
|
36
|
+
this._attr = null;
|
|
37
|
+
if (this.isClean)
|
|
38
|
+
this.dirty();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private _attr: Attr = null;
|
|
43
|
+
get attr(): Attr { return this._attr; }
|
|
44
|
+
set attr(newAttr: Attr) {
|
|
45
|
+
const oldAttr = this._attr;
|
|
46
|
+
this._attr = newAttr;
|
|
47
|
+
if (!!newAttr) {
|
|
48
|
+
if (!oldAttr || newAttr.type != oldAttr.type)
|
|
49
|
+
this.value = newAttr.defaultValue;
|
|
50
|
+
}
|
|
51
|
+
else
|
|
52
|
+
this.value = null;
|
|
53
|
+
this.attrId = newAttr?.id ?? 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
private _value: any = null;
|
|
58
|
+
get value(): any { return this._value; }
|
|
59
|
+
set value(newVal: any) {
|
|
60
|
+
if (newVal != this._value) {
|
|
61
|
+
this._value = newVal;
|
|
62
|
+
if (this.isClean)
|
|
63
|
+
this.dirty();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
private _tagId: number = null;
|
|
69
|
+
get tagId(): number { return this._tagId; }
|
|
70
|
+
set tagId(value: number) {
|
|
71
|
+
if (value !== this._tagId) {
|
|
72
|
+
this._tagId = value;
|
|
73
|
+
if (value !== this.tag?.id)
|
|
74
|
+
this._tag = null;
|
|
75
|
+
if (this.isClean)
|
|
76
|
+
this.dirty();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private _tag: Tag = null;
|
|
81
|
+
get tag(): Tag { return this._tag; }
|
|
82
|
+
set tag(value: Tag) {
|
|
83
|
+
this._tag = value;
|
|
84
|
+
this.tagId = value?.id ?? null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
duplicate(): NoteAttr {
|
|
89
|
+
const output = new NoteAttr();
|
|
90
|
+
output.noteId = this.noteId;
|
|
91
|
+
output.note = this.note;
|
|
92
|
+
output.attrId = this.attrId;
|
|
93
|
+
output.attr = this.attr;
|
|
94
|
+
output.tagId = this.tagId;
|
|
95
|
+
output.tag = this.tag;
|
|
96
|
+
output.value = this.value;
|
|
97
|
+
return output;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
validate(throwError: boolean = false): boolean {
|
|
102
|
+
let output = null;
|
|
103
|
+
|
|
104
|
+
if (this.noteId <= 0 && !this.isNew)
|
|
105
|
+
output = 'NoteAttr noteId must be greater than zero';
|
|
106
|
+
else if (this.attrId <= 0)
|
|
107
|
+
output = 'NoteAttr attrId must be greater than zero';
|
|
108
|
+
|
|
109
|
+
if (throwError && output != null)
|
|
110
|
+
throw Error(output);
|
|
111
|
+
return output == null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -2,6 +2,7 @@ import { expect, test } from 'vitest';
|
|
|
2
2
|
import NoteTag from './NoteTag';
|
|
3
3
|
import Note from './Note';
|
|
4
4
|
import Tag from './Tag';
|
|
5
|
+
import Attr from './Attr';
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
test('Gets initiated as new', () => {
|
|
@@ -197,4 +198,48 @@ test('validate succeeds if noteId & tagId are both positive, different values',
|
|
|
197
198
|
nt.noteId = 123;
|
|
198
199
|
nt.tagId = 234;
|
|
199
200
|
expect(nt.validate()).toBe(true);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
test('addAttr adds NoteAttr object to parent note', () => {
|
|
205
|
+
const note = new Note();
|
|
206
|
+
const tag = new Tag('hello').clean();
|
|
207
|
+
tag.id = 123;
|
|
208
|
+
const attr = new Attr().clean();
|
|
209
|
+
attr.id = 234;
|
|
210
|
+
const nt = note.addTag(tag);
|
|
211
|
+
|
|
212
|
+
nt.addAttr(attr);
|
|
213
|
+
|
|
214
|
+
expect(note.attrs.length).toBe(1);
|
|
215
|
+
expect(note.attrs[0].note).toBe(note);
|
|
216
|
+
expect(note.attrs[0].attr).toBe(attr);
|
|
217
|
+
expect(note.attrs[0].tag).toBe(tag);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test('addAttr throws error if note property not set', () => {
|
|
221
|
+
const nt = new NoteTag();
|
|
222
|
+
nt.noteId = 123;
|
|
223
|
+
nt.tag = new Tag('hello');
|
|
224
|
+
nt.tag.id = 234;
|
|
225
|
+
const attr = new Attr().clean();
|
|
226
|
+
attr.id = 345;
|
|
227
|
+
|
|
228
|
+
expect(() => nt.addAttr(attr)).toThrowError();
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test('attrs returns NoteAttr objects with matching tagId', () => {
|
|
232
|
+
const note = new Note();
|
|
233
|
+
const tag = new Tag('hello').clean();
|
|
234
|
+
tag.id = 123;
|
|
235
|
+
const attr1 = new Attr().clean();
|
|
236
|
+
attr1.id = 234;
|
|
237
|
+
const attr2 = new Attr().clean();
|
|
238
|
+
attr2.id = 345;
|
|
239
|
+
const nt = note.addTag(tag);
|
|
240
|
+
nt.addAttr(attr1);
|
|
241
|
+
note.addAttr(attr2);
|
|
242
|
+
|
|
243
|
+
expect(nt.attrs.length).toBe(1);
|
|
244
|
+
expect(nt.attrs[0].attr).toBe(attr1);
|
|
200
245
|
});
|
package/src/models/NoteTag.ts
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
import Note from './Note';
|
|
4
4
|
import ModelWithState from './ModelWithState';
|
|
5
5
|
import Tag from './Tag';
|
|
6
|
+
import Attr from './Attr';
|
|
7
|
+
import NoteAttr from './NoteAttr';
|
|
6
8
|
|
|
7
9
|
|
|
8
10
|
export default class NoteTag extends ModelWithState<NoteTag> {
|
|
@@ -46,6 +48,19 @@ export default class NoteTag extends ModelWithState<NoteTag> {
|
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
|
|
51
|
+
get attrs(): Array<NoteAttr> {
|
|
52
|
+
return this.note.attrs.filter(x => x.tagId == this.tagId);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
addAttr(attr: Attr): NoteAttr {
|
|
56
|
+
if (!this.note)
|
|
57
|
+
throw new Error('Cannot call addAttr on NoteTag where note property has not been set');
|
|
58
|
+
const na = this.note.addAttr(attr);
|
|
59
|
+
na.tag = this.tag;
|
|
60
|
+
return na;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
49
64
|
duplicate(): NoteTag {
|
|
50
65
|
const output = new NoteTag();
|
|
51
66
|
output.noteId = this.noteId;
|
package/src/models/Tag.test.ts
CHANGED
|
@@ -50,6 +50,26 @@ test('Tag can be initiated with name in constructor', () => {
|
|
|
50
50
|
expect(tag.name).toBe('hello');
|
|
51
51
|
});
|
|
52
52
|
|
|
53
|
+
|
|
54
|
+
test('Set color marks tag as dirty if currently clean', () => {
|
|
55
|
+
const tag = new Tag().clean();
|
|
56
|
+
tag.color = '112233';
|
|
57
|
+
expect(tag.isDirty).toBe(true);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('Set color doesnt change tag state if new', () => {
|
|
61
|
+
const tag = new Tag().new();
|
|
62
|
+
tag.color = '112233';
|
|
63
|
+
expect(tag.isNew).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('Set color doesnt change tag state if value not different', () => {
|
|
67
|
+
const tag = new Tag().clean();
|
|
68
|
+
tag.color = null;
|
|
69
|
+
expect(tag.isClean).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
|
|
53
73
|
test('can duplicate itself', () => {
|
|
54
74
|
const tag = new Tag('hello');
|
|
55
75
|
const copy = tag.duplicate();
|
|
@@ -68,4 +88,46 @@ test('validate throws error if arg set to true', () => {
|
|
|
68
88
|
const model = new Tag().clean();
|
|
69
89
|
model.id = 0;
|
|
70
90
|
expect(() => model.validate(true)).toThrowError();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('validate prevents empty name', () => {
|
|
94
|
+
const model = new Tag();
|
|
95
|
+
model.name = null;
|
|
96
|
+
expect(model.validate()).toBe(false);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('validate accepts name with letters, numbers and spaces', () => {
|
|
100
|
+
const model = new Tag();
|
|
101
|
+
model.name = 'My Tag 1';
|
|
102
|
+
expect(model.validate()).toBe(true);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('validate prevents name with special characters', () => {
|
|
106
|
+
const model = new Tag();
|
|
107
|
+
model.name = 'Hello$';
|
|
108
|
+
expect(model.validate()).toBe(false);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('validate prevents name starting with number', () => {
|
|
112
|
+
const model = new Tag();
|
|
113
|
+
model.name = '1he';
|
|
114
|
+
expect(model.validate()).toBe(false);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('validate allows null color value', () => {
|
|
118
|
+
const model = new Tag('hello');
|
|
119
|
+
model.color = null;
|
|
120
|
+
expect(model.validate()).toBe(true);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('validate allows valid color value', () => {
|
|
124
|
+
const model = new Tag('hello');
|
|
125
|
+
model.color = '#a1B2F7';
|
|
126
|
+
expect(model.validate()).toBe(true);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test('validate throws error if color is not valid hex', () => {
|
|
130
|
+
const model = new Tag('hello');
|
|
131
|
+
model.color = 'A9C10G8';
|
|
132
|
+
expect(model.validate()).toBe(false);
|
|
71
133
|
});
|
package/src/models/Tag.ts
CHANGED
|
@@ -26,6 +26,17 @@ export default class Tag extends ModelWithState<Tag> {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
|
|
29
|
+
private _color: string = null;
|
|
30
|
+
get color(): string { return this._color; }
|
|
31
|
+
set color(value: string) {
|
|
32
|
+
if (value !== this._color) {
|
|
33
|
+
this._color = value;
|
|
34
|
+
if (this.isClean)
|
|
35
|
+
this.dirty();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
29
40
|
constructor(name: string = '') {
|
|
30
41
|
super();
|
|
31
42
|
this._name = name;
|
|
@@ -46,6 +57,10 @@ export default class Tag extends ModelWithState<Tag> {
|
|
|
46
57
|
|
|
47
58
|
if (!this.isNew && this.id <= 0)
|
|
48
59
|
output = 'Tag id must be greater than zero if in non-new state.';
|
|
60
|
+
else if (!this.name || !/^[a-zA-Z][a-zA-Z0-9 ]*[a-zA-Z0-9]?$/.test(this.name))
|
|
61
|
+
output = 'Tag name is invalid, must only contain letters, numbers, and spaces, starting with a letter';
|
|
62
|
+
else if (!!this.color && !/^#?[A-z0-9]{6}$/.test(this.color))
|
|
63
|
+
output = 'Tag color is invalid, must be a 6 character hexadecimal.';
|
|
49
64
|
|
|
50
65
|
if (throwError && output != null)
|
|
51
66
|
throw Error(output);
|