notu 0.1.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.
@@ -0,0 +1,322 @@
1
+ import { expect, test } from 'vitest';
2
+ import Note from './Note';
3
+ import Tag from './Tag';
4
+ import Space from './Space';
5
+
6
+
7
+ function newCleanTag(): Tag {
8
+ const tag = new Tag('hello');
9
+ tag.id = 123;
10
+ tag.clean();
11
+ return tag;
12
+ }
13
+
14
+
15
+ test('gets initiated with sensible defaults', () => {
16
+ const note = new Note();
17
+ expect(note.id).toBe(0);
18
+ expect(note.date.getTime() / 1000).toBeCloseTo(new Date().getTime() / 1000);
19
+ expect(note.text).toBe('');
20
+ expect(note.archived).toBe(false);
21
+ expect(note.spaceId).toBe(0);
22
+ });
23
+
24
+ test('can duplicate itself', () => {
25
+ const note = new Note().clean();
26
+ const space = new Space('hello');
27
+ space.id = 123;
28
+ note.space = space;
29
+ const copy = note.duplicate();
30
+ expect(copy.id).toBe(note.id);
31
+ expect(copy.date).toBe(note.date);
32
+ expect(copy.text).toBe(note.text);
33
+ expect(copy.archived).toBe(note.archived);
34
+ expect(copy.space).toBe(note.space);
35
+ expect(copy.spaceId).toBe(note.spaceId);
36
+ expect(copy.state).toBe(note.state);
37
+ });
38
+
39
+ test('Gets initiated as new', () => {
40
+ const note = new Note();
41
+ expect(note.isNew).toBe(true);
42
+ });
43
+
44
+
45
+ test('Set date marks note as dirty if currently clean', () => {
46
+ const note = new Note().clean();
47
+ note.date = new Date();
48
+ expect(note.isDirty).toBe(true);
49
+ });
50
+
51
+ test('Set date doesnt change note state if new', () => {
52
+ const note = new Note().new();
53
+ note.date = new Date();
54
+ expect(note.isNew).toBe(true);
55
+ });
56
+
57
+ test('Set date doesnt change note state if value not different', () => {
58
+ const note = new Note().clean();
59
+ note.date = note.date;
60
+ expect(note.isClean).toBe(true);
61
+ });
62
+
63
+
64
+ test('Set text marks note as dirty if currently clean', () => {
65
+ const note = new Note().clean();
66
+ note.text = 'asdf';
67
+ expect(note.isDirty).toBe(true);
68
+ });
69
+
70
+ test('Set text doesnt change note state if new', () => {
71
+ const note = new Note().new();
72
+ note.text = 'asdf';
73
+ expect(note.isNew).toBe(true);
74
+ });
75
+
76
+ test('Set text doesnt change note state if value not different', () => {
77
+ const note = new Note().clean();
78
+ note.text = '';
79
+ expect(note.isClean).toBe(true);
80
+ });
81
+
82
+
83
+ test('Set archived marks note as dirty if currently clean', () => {
84
+ const note = new Note().clean();
85
+ note.archived = !note.archived;
86
+ expect(note.isDirty).toBe(true);
87
+ });
88
+
89
+ test('Set archived doesnt change note state if new', () => {
90
+ const note = new Note().new();
91
+ note.archived = !note.archived;
92
+ expect(note.isNew).toBe(true);
93
+ });
94
+
95
+ test('Set archived doesnt change note state if value not different', () => {
96
+ const note = new Note().clean();
97
+ note.archived = note.archived;
98
+ expect(note.isClean).toBe(true);
99
+ });
100
+
101
+
102
+ test('Set spaceId marks note as dirty if currently clean', () => {
103
+ const note = new Note().clean();
104
+ note.spaceId = 123;
105
+ expect(note.isDirty).toBe(true);
106
+ });
107
+
108
+ test('Set spaceId doesnt change note state if new', () => {
109
+ const note = new Note().new();
110
+ note.spaceId = 123;
111
+ expect(note.isNew).toBe(true);
112
+ });
113
+
114
+ test('Set spaceId doesnt change note state if value not different', () => {
115
+ const note = new Note().clean();
116
+ note.spaceId = note.spaceId;
117
+ expect(note.isClean).toBe(true);
118
+ });
119
+
120
+ test('setOwnTag doesnt mark note as dirty', () => {
121
+ const note = new Note().clean().setOwnTag('hello');
122
+ expect(note.isClean).toBe(true);
123
+ });
124
+
125
+ test('setOwnTag with string creates tag with same id as note', () => {
126
+ const note = new Note();
127
+ note.id = 123;
128
+ note.setOwnTag('hello');
129
+ expect(note.ownTag.id).toBe(note.id);
130
+ });
131
+
132
+ test('setOwnTag can take tag object, rather than just name', () => {
133
+ const note = new Note().setOwnTag(new Tag('hello'));
134
+ expect(note.ownTag.name).toBe('hello');
135
+ });
136
+
137
+ test('setOwnTag with tag object will throw error if tag already set', () => {
138
+ const note = new Note().setOwnTag('hello');
139
+ expect(() => note.setOwnTag(new Tag('goodbye'))).toThrowError();
140
+ });
141
+
142
+ test('setOwnTag with tag object will throw error if tag id is non-zero and doesnt match note id', () => {
143
+ const note = new Note().clean();
144
+ note.id = 123;
145
+ const tag = new Tag('hello');
146
+ tag.id = 57;
147
+ expect(() => note.setOwnTag(tag)).toThrowError();
148
+ });
149
+
150
+ test('removeOwnTag marks existing tag as deleted if clean', () => {
151
+ const note = new Note().setOwnTag('hello');
152
+ note.ownTag.clean();
153
+ note.removeOwnTag();
154
+ expect(note.ownTag.isDeleted).toBe(true);
155
+ });
156
+
157
+ test('removeOwnTag nulls out new tag', () => {
158
+ const note = new Note().setOwnTag('hello');
159
+ note.removeOwnTag();
160
+ expect(note.ownTag).toBeNull();
161
+ });
162
+
163
+ test('Setting space with id different than current spaceId updates state', () => {
164
+ const note = new Note();
165
+ note.spaceId = 57;
166
+ note.clean();
167
+ const space = new Space('hello');
168
+ space.id = 60;
169
+
170
+ note.space = space;
171
+
172
+ expect(note.spaceId).toBe(60);
173
+ expect(note.isDirty).toBe(true);
174
+ });
175
+
176
+ test('Setting space with id same as current spaceId preserves state', () => {
177
+ const note = new Note();
178
+ note.spaceId = 80;
179
+ note.clean();
180
+ const space = new Space('hello');
181
+ space.id = 80;
182
+
183
+ note.space = space;
184
+
185
+ expect(note.spaceId).toBe(80);
186
+ expect(note.isClean).toBe(true);
187
+ });
188
+
189
+ test('Setting spaceId to new value removes space object', () => {
190
+ const note = new Note();
191
+ const space = new Space('hello');
192
+ space.id = 80;
193
+ note.space = space;
194
+
195
+ note.spaceId = 81;
196
+
197
+ expect(note.space).toBeNull();
198
+ });
199
+
200
+ test('Setting spaceId to same as current space id preserves it', () => {
201
+ const note = new Note();
202
+ const space = new Space('hello');
203
+ space.id = 80;
204
+ note.space = space;
205
+
206
+ note.spaceId = 80;
207
+
208
+ expect(note.space.name).toBe('hello');
209
+ });
210
+
211
+
212
+ test('validate fails if spaceId is 0', () => {
213
+ const model = new Note();
214
+ model.spaceId = 0;
215
+ expect(model.validate()).toBe(false);
216
+ });
217
+
218
+ test('validate fails if not new and id <= 0', () => {
219
+ const model = new Note().clean();
220
+ model.id = 0;
221
+ model.spaceId = 123;
222
+ expect(model.validate()).toBe(false);
223
+ });
224
+
225
+ test('validate throws error if arg set to true', () => {
226
+ const model = new Note();
227
+ model.spaceId = 0;
228
+ expect(() => model.validate(true)).toThrowError();
229
+ });
230
+
231
+ test('validate calls validate on ownTag', () => {
232
+ const note = new Note().setOwnTag('asdf');
233
+ note.spaceId = 123;
234
+ expect(note.validate()).toBe(true);
235
+ note.ownTag.clean();
236
+ expect(note.validate()).toBe(false);
237
+ });
238
+
239
+ test('validate calls validate on each added tag', () => {
240
+ const note = new Note();
241
+ note.spaceId = 123;
242
+ const nt = note.addTag(newCleanTag());
243
+ expect(note.validate()).toBe(true);
244
+ nt.tagId = 0;
245
+ expect(note.validate()).toBe(false);
246
+ });
247
+
248
+
249
+ test('addTag adds new NoteTag object', () => {
250
+ const tag = newCleanTag();
251
+ const note = new Note();
252
+
253
+ note.addTag(tag);
254
+
255
+ expect(note.tags.length).toBe(1);
256
+ expect(note.tags[0].note).toBe(note);
257
+ expect(note.tags[0].tag).toBe(tag);
258
+ });
259
+
260
+ test('addTag returns existing NoteTag object if trying to add duplicate tag', () => {
261
+ const tag = newCleanTag();
262
+ const note = new Note();
263
+ note.addTag(tag);
264
+
265
+ note.addTag(tag);
266
+
267
+ expect(note.tags.length).toBe(1);
268
+ expect(note.tags[0].note).toBe(note);
269
+ expect(note.tags[0].tag).toBe(tag);
270
+ });
271
+
272
+ test('addTag undeletes existing NoteTag if trying to add duplicate tag', () => {
273
+ const tag = newCleanTag();
274
+ const note = new Note();
275
+ const nt = note.addTag(tag);
276
+ nt.delete();
277
+
278
+ note.addTag(tag);
279
+
280
+ expect(note.tags.length).toBe(1);
281
+ expect(nt.isDirty).toBe(true);
282
+ });
283
+
284
+ test('addTag throws error if trying to add deleted tag', () => {
285
+ const tag = newCleanTag().delete();
286
+ const note = new Note();
287
+ expect(() => note.addTag(tag)).toThrowError();
288
+ });
289
+
290
+ test('addTag prevents note from adding its own tag', () => {
291
+ const note = new Note();
292
+ note.id = 123;
293
+ note.setOwnTag('test');
294
+
295
+ expect(() => note.addTag(note.ownTag)).toThrowError();
296
+ });
297
+
298
+ test('addTag prevents note from adding tag that hasnt been saved yet', () => {
299
+ const note = new Note();
300
+ expect(() => note.addTag(new Tag())).toThrowError();
301
+ });
302
+
303
+ test('removeTag removes newly added tag from note', () => {
304
+ const tag = newCleanTag();
305
+ const note = new Note();
306
+ note.addTag(tag);
307
+
308
+ note.removeTag(tag);
309
+
310
+ expect(note.tags.length).toBe(0);
311
+ });
312
+
313
+ test('removeTag marks existing tag on note as deleted', () => {
314
+ const tag = newCleanTag();
315
+ const note = new Note();
316
+ note.addTag(tag).clean();
317
+
318
+ note.removeTag(tag);
319
+
320
+ expect(note.tags.length).toBe(1);
321
+ expect(note.tags[0].isDeleted).toBe(true);
322
+ });
@@ -0,0 +1,164 @@
1
+ 'use strict';
2
+
3
+ import ModelWithState from './ModelWithState';
4
+ import NoteTag from './NoteTag';
5
+ import Space from './Space';
6
+ import Tag from './Tag';
7
+
8
+
9
+ export default class Note extends ModelWithState<Note> {
10
+ id: number = 0;
11
+
12
+
13
+ private _date: Date = new Date();
14
+ get date(): Date { return this._date; }
15
+ set date(value: Date) {
16
+ if (value !== this._date) {
17
+ this._date = value;
18
+ if (this.isClean)
19
+ this.dirty();
20
+ }
21
+ }
22
+
23
+
24
+ private _text: string = '';
25
+ get text(): string { return this._text; }
26
+ set text(value: string) {
27
+ if (value !== this._text) {
28
+ this._text = value;
29
+ if (this.isClean)
30
+ this.dirty();
31
+ }
32
+ }
33
+
34
+
35
+ private _archived: boolean = false;
36
+ get archived(): boolean { return this._archived; }
37
+ set archived(value: boolean) {
38
+ if (value !== this._archived) {
39
+ this._archived = value;
40
+ if (this.isClean)
41
+ this.dirty();
42
+ }
43
+ }
44
+
45
+
46
+ private _spaceId: number = 0;
47
+ get spaceId(): number { return this._spaceId; }
48
+ set spaceId(value: number) {
49
+ if (value !== this._spaceId) {
50
+ this._spaceId = value;
51
+ if (value !== this.space?.id ?? 0)
52
+ this._space = null;
53
+ if (this.isClean)
54
+ this.dirty();
55
+ }
56
+ }
57
+
58
+ private _space: Space = null;
59
+ get space(): Space { return this._space; }
60
+ set space(value: Space) {
61
+ this._space = value;
62
+ this.spaceId = value?.id ?? 0;
63
+ }
64
+
65
+
66
+ private _ownTag: Tag = null;
67
+ get ownTag(): Tag { return this._ownTag; }
68
+
69
+ setOwnTag(tag: string | Tag): Note {
70
+ if (typeof tag === 'string') {
71
+ if (this.ownTag == null)
72
+ this._ownTag = new Tag();
73
+ this.ownTag.name = tag;
74
+ this.ownTag.id = this.id;
75
+ }
76
+ else {
77
+ if (!!this.ownTag)
78
+ throw new Error('Note has already had its tag set. If you would like to change the tag name, call setTag with just a string specifying the new tag name.');
79
+ if (tag.id != 0 && tag.id != this.id)
80
+ throw new Error('Attempted to set tag to note with non-matching ID. Added tag id must either match the note id, which indicates that the tag has already been added to the note. Otherwise the tag id must be zero, indicating that the tag still needs to be added.')
81
+ this._ownTag = tag;
82
+ }
83
+ return this;
84
+ }
85
+
86
+ removeOwnTag(): Note {
87
+ if (!this.ownTag)
88
+ return;
89
+ if (this.ownTag.isNew)
90
+ this._ownTag = null;
91
+ else
92
+ this.ownTag.delete();
93
+ }
94
+
95
+
96
+ private _tags: Array<NoteTag> = [];
97
+ get tags(): Array<NoteTag> { return this._tags; }
98
+
99
+ addTag(tag: Tag): NoteTag {
100
+ if (tag.isDeleted)
101
+ throw Error('Cannot add a tag marked as deleted to a note');
102
+ if (tag.isNew)
103
+ throw Error('Cannot add a tag that hasn\'t yet been saved to a note');
104
+ if (tag.id == this.id)
105
+ throw Error('Note cannot add its own tag as a linked tag');
106
+ let nt = this.tags.find(x => x.tagId == tag.id);
107
+ if (!!nt) {
108
+ if (nt.isDeleted)
109
+ nt.dirty();
110
+ return nt;
111
+ }
112
+ nt = new NoteTag();
113
+ nt.note = this;
114
+ nt.tag = tag;
115
+ this._tags.push(nt);
116
+ return nt;
117
+ }
118
+
119
+ removeTag(tag: Tag): Note {
120
+ const nt = this.tags.find(x => x.tagId == tag.id);
121
+ if (!nt)
122
+ return this;
123
+
124
+ if (nt.isNew)
125
+ this._tags = this._tags.filter(x => x !== nt);
126
+ else
127
+ nt.delete();
128
+ return this;
129
+ }
130
+
131
+
132
+ duplicate(): Note {
133
+ const output = new Note();
134
+ output.id = this.id;
135
+ output.date = this.date;
136
+ output.text = this.text;
137
+ output.archived = this.archived;
138
+ output.space = this.space;
139
+ output.state = this.state;
140
+ return output;
141
+ }
142
+
143
+
144
+ validate(throwError: boolean = false): boolean {
145
+ let output = null;
146
+
147
+ if (this.spaceId <= 0)
148
+ output = 'Note spaceId must be greater than zero.';
149
+ else if (!this.isNew && this.id <= 0)
150
+ output = 'Note id must be greater than zero if in non-new state.';
151
+
152
+ if (throwError && output != null)
153
+ throw Error(output);
154
+
155
+ if (!!this.ownTag && !this.ownTag.validate(throwError))
156
+ return false;
157
+ for (const nt of this.tags) {
158
+ if (!nt.validate(throwError))
159
+ return false;
160
+ }
161
+
162
+ return output == null;
163
+ }
164
+ }
@@ -0,0 +1,200 @@
1
+ import { expect, test } from 'vitest';
2
+ import NoteTag from './NoteTag';
3
+ import Note from './Note';
4
+ import Tag from './Tag';
5
+
6
+
7
+ test('Gets initiated as new', () => {
8
+ const nt = new NoteTag();
9
+ expect(nt.isNew).toBe(true);
10
+ });
11
+
12
+ test('Gets initiated with sensible defaults', () => {
13
+ const nt = new NoteTag();
14
+ expect(nt.noteId).toBe(0);
15
+ expect(nt.tagId).toBe(0);
16
+ });
17
+
18
+ test('Can duplicate itself', () => {
19
+ const nt = new NoteTag();
20
+ nt.noteId = 123;
21
+ nt.tagId = 234;
22
+ const copy = nt.duplicate();
23
+ expect(copy.noteId).toBe(nt.noteId);
24
+ expect(copy.tagId).toBe(nt.tagId);
25
+ });
26
+
27
+
28
+ test('Set noteId marks object as dirty if currently clean', () => {
29
+ const nt = new NoteTag().clean();
30
+ nt.noteId = 123;
31
+ expect(nt.isDirty).toBe(true);
32
+ });
33
+
34
+ test('Set noteId doesnt change state if new', () => {
35
+ const nt = new NoteTag().new();
36
+ nt.noteId = 123;
37
+ expect(nt.isNew).toBe(true);
38
+ });
39
+
40
+ test('Set noteId doesnt change state if value not different', () => {
41
+ const nt = new NoteTag().clean();
42
+ nt.noteId = nt.noteId;
43
+ expect(nt.isClean).toBe(true);
44
+ });
45
+
46
+
47
+ test('Set tagId marks object as dirty if currently clean', () => {
48
+ const nt = new NoteTag().clean();
49
+ nt.tagId = 123;
50
+ expect(nt.isDirty).toBe(true);
51
+ });
52
+
53
+ test('Set tagId doesnt change state if new', () => {
54
+ const nt = new NoteTag().new();
55
+ nt.tagId = 123;
56
+ expect(nt.isNew).toBe(true);
57
+ });
58
+
59
+ test('Set tagId doesnt change state if value not different', () => {
60
+ const nt = new NoteTag().clean();
61
+ nt.tagId = nt.tagId;
62
+ expect(nt.isClean).toBe(true);
63
+ });
64
+
65
+
66
+ test('Setting note with id different than current noteId updates state', () => {
67
+ const nt = new NoteTag();
68
+ nt.noteId = 123;
69
+ nt.clean();
70
+ const note = new Note();
71
+ note.id = 234;
72
+
73
+ nt.note = note;
74
+
75
+ expect(nt.noteId).toBe(234);
76
+ expect(nt.isDirty).toBe(true);
77
+ });
78
+
79
+ test('Setting note with id same as current noteId preserves state', () => {
80
+ const nt = new NoteTag();
81
+ nt.noteId = 27;
82
+ nt.clean();
83
+ const note = new Note();
84
+ note.id = 27;
85
+
86
+ nt.note = note;
87
+
88
+ expect(nt.noteId).toBe(27);
89
+ expect(nt.isClean).toBe(true);
90
+ });
91
+
92
+ test('Setting noteId to new value removes note object', () => {
93
+ const nt = new NoteTag();
94
+ const note = new Note();
95
+ note.id = 80;
96
+ nt.note = note;
97
+
98
+ nt.noteId = 81;
99
+
100
+ expect(nt.note).toBeNull();
101
+ });
102
+
103
+ test('Setting noteId to same as current note id preserves it', () => {
104
+ const nt = new NoteTag();
105
+ const note = new Note();
106
+ note.id = 80;
107
+ nt.note = note;
108
+
109
+ nt.noteId = 80;
110
+
111
+ expect(nt.note).not.toBeNull();
112
+ });
113
+
114
+
115
+ test('Setting tag with id different than current tagId updates state', () => {
116
+ const nt = new NoteTag();
117
+ nt.tagId = 123;
118
+ nt.clean();
119
+ const tag = new Tag();
120
+ tag.id = 234;
121
+
122
+ nt.tag = tag;
123
+
124
+ expect(nt.tagId).toBe(234);
125
+ expect(nt.isDirty).toBe(true);
126
+ });
127
+
128
+ test('Setting tag with id same as current tagId preserves state', () => {
129
+ const nt = new NoteTag();
130
+ nt.tagId = 27;
131
+ nt.clean();
132
+ const tag = new Tag();
133
+ tag.id = 27;
134
+
135
+ nt.tag = tag;
136
+
137
+ expect(nt.tagId).toBe(27);
138
+ expect(nt.isClean).toBe(true);
139
+ });
140
+
141
+ test('Setting tagId to new value removes tag object', () => {
142
+ const nt = new NoteTag();
143
+ const tag = new Tag();
144
+ tag.id = 80;
145
+ nt.tag = tag;
146
+
147
+ nt.tagId = 81;
148
+
149
+ expect(nt.tag).toBeNull();
150
+ });
151
+
152
+ test('Setting tagId to same as current tag id preserves it', () => {
153
+ const nt = new NoteTag();
154
+ const tag = new Tag();
155
+ tag.id = 80;
156
+ nt.tag = tag;
157
+
158
+ nt.tagId = 80;
159
+
160
+ expect(nt.tag).not.toBeNull();
161
+ });
162
+
163
+
164
+ test('validate passes if noteId is 0 and state is new', () => {
165
+ const nt = new NoteTag();
166
+ nt.tagId = 123;
167
+ expect(nt.validate()).toBe(true);
168
+ });
169
+
170
+ test('validate fails if noteId is 0 and state is not new', () => {
171
+ const nt = new NoteTag();
172
+ nt.tagId = 123;
173
+ nt.clean();
174
+ expect(nt.validate()).toBe(false);
175
+ });
176
+
177
+ test('validate fails if tagId is 0', () => {
178
+ const nt = new NoteTag();
179
+ nt.noteId = 123;
180
+ expect(nt.validate()).toBe(false);
181
+ });
182
+
183
+ test('validate throws error if arg set to true', () => {
184
+ const nt = new NoteTag();
185
+ expect(() => nt.validate(true)).toThrowError();
186
+ });
187
+
188
+ test('validate fails if noteId = tagId', () => {
189
+ const nt = new NoteTag();
190
+ nt.noteId = 123;
191
+ nt.tagId = 123;
192
+ expect(nt.validate()).toBe(false);
193
+ });
194
+
195
+ test('validate succeeds if noteId & tagId are both positive, different values', () => {
196
+ const nt = new NoteTag();
197
+ nt.noteId = 123;
198
+ nt.tagId = 234;
199
+ expect(nt.validate()).toBe(true);
200
+ });