theshtify 1.0.4 → 2.0.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,228 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
2
+ import { theshtify } from "../lib/theshtify.js";
3
+
4
+ beforeEach(() => {
5
+ document.body.innerHTML = "";
6
+ document.querySelectorAll(".Thesharsol-notifyer").forEach((el) => el.remove());
7
+ });
8
+
9
+ afterEach(() => {
10
+ document.querySelectorAll(".Thesharsol-notifyer").forEach((el) => el.remove());
11
+ });
12
+
13
+ describe("basic notification creation", () => {
14
+ it("should create a notification element in the DOM", () => {
15
+ theshtify({ message: "Hello", type: "success" });
16
+ const notifs = document.querySelectorAll(".Thesharsol-notifyer");
17
+ expect(notifs.length).toBe(1);
18
+ });
19
+
20
+ it("should display the correct message", () => {
21
+ theshtify({ message: "Test message", type: "info" });
22
+ const notif = document.querySelector(".Thesharsol-notifyer");
23
+ expect(notif.innerHTML).toContain("Test message");
24
+ });
25
+
26
+ it("should display the title when provided", () => {
27
+ theshtify({ message: "Body text", type: "success", title: "My Title" });
28
+ const notif = document.querySelector(".Thesharsol-notifyer");
29
+ expect(notif.innerHTML).toContain("My Title");
30
+ });
31
+
32
+ it("should create multiple stacked notifications", () => {
33
+ theshtify({ message: "First", type: "success" });
34
+ theshtify({ message: "Second", type: "danger" });
35
+ const notifs = document.querySelectorAll(".Thesharsol-notifyer");
36
+ expect(notifs.length).toBe(2);
37
+ });
38
+
39
+ it("should support all built-in types", () => {
40
+ const types = ["success", "danger", "info", "warning"];
41
+ types.forEach((type) => {
42
+ theshtify({ message: `Type ${type}`, type });
43
+ });
44
+ const notifs = document.querySelectorAll(".Thesharsol-notifyer");
45
+ expect(notifs.length).toBe(4);
46
+ });
47
+ });
48
+
49
+ describe("config validation", () => {
50
+ it("should still create a notification without a message (uses default)", () => {
51
+ theshtify({ type: "success" });
52
+ const notifs = document.querySelectorAll(".Thesharsol-notifyer");
53
+ expect(notifs.length).toBe(1);
54
+ });
55
+
56
+ it("should still create a notification without a type (uses default)", () => {
57
+ theshtify({ message: "Hello" });
58
+ const notifs = document.querySelectorAll(".Thesharsol-notifyer");
59
+ expect(notifs.length).toBe(1);
60
+ });
61
+
62
+ it("should not create a notification with an invalid type", () => {
63
+ const spy = vi.spyOn(console, "error").mockImplementation(() => {});
64
+ theshtify({ message: "Hello", type: "invalid" });
65
+ const notifs = document.querySelectorAll(".Thesharsol-notifyer");
66
+ expect(notifs.length).toBe(0);
67
+ spy.mockRestore();
68
+ });
69
+
70
+ it("should accept a valid duration", () => {
71
+ theshtify({
72
+ message: "Hello",
73
+ type: "success",
74
+ config: { duration: 3000 },
75
+ });
76
+ const notifs = document.querySelectorAll(".Thesharsol-notifyer");
77
+ expect(notifs.length).toBe(1);
78
+ });
79
+
80
+ it("should reject a non-boolean bordered value", () => {
81
+ const spy = vi.spyOn(console, "error").mockImplementation(() => {});
82
+ theshtify({
83
+ message: "Hello",
84
+ type: "success",
85
+ config: { bordered: "yes" },
86
+ });
87
+ const notifs = document.querySelectorAll(".Thesharsol-notifyer");
88
+ expect(notifs.length).toBe(0);
89
+ spy.mockRestore();
90
+ });
91
+
92
+ it("should reject a non-boolean progress value", () => {
93
+ const spy = vi.spyOn(console, "error").mockImplementation(() => {});
94
+ theshtify({
95
+ message: "Hello",
96
+ type: "success",
97
+ config: { progress: "yes" },
98
+ });
99
+ const notifs = document.querySelectorAll(".Thesharsol-notifyer");
100
+ expect(notifs.length).toBe(0);
101
+ spy.mockRestore();
102
+ });
103
+
104
+ it("should reject a non-number radius", () => {
105
+ const spy = vi.spyOn(console, "error").mockImplementation(() => {});
106
+ theshtify({
107
+ message: "Hello",
108
+ type: "success",
109
+ config: { radius: "big" },
110
+ });
111
+ const notifs = document.querySelectorAll(".Thesharsol-notifyer");
112
+ expect(notifs.length).toBe(0);
113
+ spy.mockRestore();
114
+ });
115
+ });
116
+
117
+ describe("positioning", () => {
118
+ it("should accept valid x_align values", () => {
119
+ ["left", "right", "middle"].forEach((x_align) => {
120
+ theshtify({ message: "Hello", type: "success", config: { x_align } });
121
+ });
122
+ const notifs = document.querySelectorAll(".Thesharsol-notifyer");
123
+ expect(notifs.length).toBe(3);
124
+ });
125
+
126
+ it("should accept valid y_align values", () => {
127
+ ["top", "bottom", "middle"].forEach((y_align) => {
128
+ theshtify({ message: "Hello", type: "success", config: { y_align } });
129
+ });
130
+ const notifs = document.querySelectorAll(".Thesharsol-notifyer");
131
+ expect(notifs.length).toBe(3);
132
+ });
133
+
134
+ it("should reject invalid x_align", () => {
135
+ const spy = vi.spyOn(console, "error").mockImplementation(() => {});
136
+ theshtify({
137
+ message: "Hello",
138
+ type: "success",
139
+ config: { x_align: "center" },
140
+ });
141
+ const notifs = document.querySelectorAll(".Thesharsol-notifyer");
142
+ expect(notifs.length).toBe(0);
143
+ spy.mockRestore();
144
+ });
145
+
146
+ it("should reject invalid y_align", () => {
147
+ const spy = vi.spyOn(console, "error").mockImplementation(() => {});
148
+ theshtify({
149
+ message: "Hello",
150
+ type: "success",
151
+ config: { y_align: "center" },
152
+ });
153
+ const notifs = document.querySelectorAll(".Thesharsol-notifyer");
154
+ expect(notifs.length).toBe(0);
155
+ spy.mockRestore();
156
+ });
157
+ });
158
+
159
+ describe("features", () => {
160
+ it("should show close button when closer is true", () => {
161
+ theshtify({
162
+ message: "Hello",
163
+ type: "success",
164
+ config: { closer: true },
165
+ });
166
+ const closeBtn = document.querySelector(".close-icon");
167
+ expect(closeBtn).not.toBeNull();
168
+ });
169
+
170
+ it("should show progress bar when progress is true", () => {
171
+ theshtify({
172
+ message: "Hello",
173
+ type: "success",
174
+ config: { progress: true },
175
+ });
176
+ const progress = document.querySelector(".progress");
177
+ expect(progress).not.toBeNull();
178
+ });
179
+
180
+ it("should apply custom CSS class via main_box_classes", () => {
181
+ theshtify({
182
+ message: "Hello",
183
+ type: "success",
184
+ config: { main_box_classes: "my-custom-class" },
185
+ });
186
+ const notif = document.querySelector(".Thesharsol-notifyer");
187
+ expect(notif.classList.contains("my-custom-class")).toBe(true);
188
+ });
189
+
190
+ it("should set the callback and trigger on click", () => {
191
+ const fn = vi.fn();
192
+ theshtify({
193
+ message: "Hello",
194
+ type: "success",
195
+ callback: fn,
196
+ });
197
+ const notif = document.querySelector(".Thesharsol-notifyer");
198
+ notif.click();
199
+ expect(fn).toHaveBeenCalled();
200
+ });
201
+
202
+ it("should apply border when bordered is true", () => {
203
+ theshtify({
204
+ message: "Hello",
205
+ type: "success",
206
+ config: { bordered: true },
207
+ });
208
+ const notif = document.querySelector(".Thesharsol-notifyer");
209
+ expect(notif.style.border).not.toBe("");
210
+ });
211
+
212
+ it("should apply custom border radius", () => {
213
+ theshtify({
214
+ message: "Hello",
215
+ type: "success",
216
+ config: { radius: 20 },
217
+ });
218
+ const notif = document.querySelector(".Thesharsol-notifyer");
219
+ expect(notif.style.borderRadius).toBe("20px");
220
+ });
221
+
222
+ it("should use responsive width with max-width", () => {
223
+ theshtify({ message: "Hello", type: "success" });
224
+ const notif = document.querySelector(".Thesharsol-notifyer");
225
+ expect(notif.style.width).toBe("90%");
226
+ expect(notif.style.maxWidth).toBe("300px");
227
+ });
228
+ });
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ environment: "jsdom",
6
+ },
7
+ });
package/lib/notif.js DELETED
@@ -1,347 +0,0 @@
1
-
2
- class Notif {
3
- #config = {
4
- x_pos: 'right',
5
- y_pos: 'top',
6
- font: {
7
- family: 'arial',
8
- size: 14,
9
- weight: ''
10
- },
11
- max_duration: 5000,
12
- duration: 5000,
13
- borderWidth: 2,
14
- bordered: false,
15
- progress: false,
16
- progressHeight: 2,
17
- closer: false,
18
- trans: false,
19
- display: {
20
- width: 300,
21
- colors: {
22
- custom: {
23
- color: 'white',
24
- bg: '#000',
25
- border: {
26
- type: 'solid',
27
- color: 'transparent',
28
- },
29
- progress: {
30
- bg: '#fff'
31
- }
32
- },
33
- success: {
34
- color: 'white',
35
- bg: 'rgba(1, 191, 102, 0.822)',
36
- border: {
37
- type: 'solid',
38
- color: 'transparent',
39
- },
40
- progress: {
41
- bg: '#fff'
42
- }
43
- },
44
- danger: {
45
- bg: 'red',
46
- color: 'white',
47
- border: {
48
- type: 'solid',
49
- color: 'transparent',
50
- },
51
- progress: {
52
- bg: '#fff'
53
- }
54
- },
55
- info: {
56
- bg: 'rgba(7, 133, 250, 0.822)',
57
- color: 'white',
58
- border: {
59
- type: 'solid',
60
- color: 'transparent',
61
- },
62
- progress: {
63
- bg: '#fff'
64
- }
65
- },
66
- warning: {
67
- bg: '#f89406',
68
- color: 'black',
69
- border: {
70
- type: 'solid',
71
- color: 'transparent',
72
- },
73
- progress: {
74
- bg: '#fff'
75
- }
76
- },
77
- },
78
- padding: {
79
- left: 10,
80
- right: 10,
81
- top: 10,
82
- bottom: 10
83
- },
84
- margin: {
85
- left: 20,
86
- right: 20,
87
- top: 20,
88
- bottom: 20
89
- },
90
- radius: 5,
91
- }
92
- };
93
- #type;
94
- #message;
95
- constructor(config) {
96
- this.config = config;
97
- this.type = "danger";
98
- this.message = "notification";
99
- }
100
- get config() {
101
- return this.#config;
102
- }
103
- set config(config) {
104
- for (const key in config) {
105
- if (Object.hasOwnProperty.call(this.#config, key)) {
106
- this.#config[key] == config[key];
107
- }
108
- }
109
- }
110
- set duration(duration) {
111
- let c_duration = parseInt(duration);
112
- if (c_duration != NaN) {
113
- c_duration <= this.config.max_duration ?
114
- this.config.duration = c_duration :
115
- console.error('the max duration has to be 5the duration of the notification provided is longer than the maximum posting duration and has therefore been set at the maximum value.');
116
- }
117
- }
118
- configure(config) {
119
- config.message ? this.message = config.message : '';
120
- config.type ? this.type = config.type : '';
121
- (config.colors != undefined && config.type == 'custom') ? this.config.display.colors.custom = config.colors : '';
122
- config.duration ? this.duration = config.duration : '';
123
- config.x_align ? this.config.x_pos = config.x_align : '';
124
- config.y_align ? this.config.y_pos = config.y_align : '';
125
- config.bordered ? this.config.bordered = config.bordered : '';
126
- config.closer ? this.config.closer = config.closer : '';
127
- config.progress ? this.config.progress = config.progress : '';
128
- (config.bordered && config.colors != undefined && config.type == 'custom' && config.colors.border != undefined) ? this.config.display.colors.custom.border = config.colors.border : '';
129
-
130
- }
131
- get message() {
132
- return this.#message;
133
- }
134
- set message(message) {
135
- this.#message = message;
136
- }
137
- get type() {
138
- return this.#type;
139
- }
140
- set type(type) {
141
- this.#type = type;
142
- }
143
-
144
- getPosition(boxSize) {
145
- let retPos = {
146
- x: 0,
147
- y: 0
148
- }
149
- this.config.x_pos == 'left' ? retPos.x = this.config.display.margin.left : 0;
150
- this.config.x_pos == 'right' ? retPos.x = window.innerWidth - (boxSize.width + this.config.display.margin.right) : 0;
151
- this.config.x_pos == 'middle' ? retPos.x = (window.innerWidth - boxSize.width) / 2 : 0;
152
- this.config.y_pos == 'top' ? retPos.y = 0 + this.config.display.margin.top : 0;
153
- this.config.y_pos == 'bottom' ? retPos.y = window.innerHeight - (boxSize.height + this.config.display.margin.bottom) : 0;
154
- this.config.y_pos == 'middle' ? retPos.y = (window.innerHeight - boxSize.height) / 2 : 0;
155
- return retPos;
156
- }
157
- moveDisplayedNotifications(createdBox, moveType = "add", startIndex = 0) {
158
- let notifBoxes = document.querySelectorAll('.Thesharsol-notifyer');
159
- let offset = createdBox.offsetHeight + 20;
160
- //
161
- let boxesCumulatedHeihgt = offset;
162
-
163
- switch (moveType) {
164
- case "add":
165
- for (let i = startIndex; i < notifBoxes.length; i++) {
166
- boxesCumulatedHeihgt = this.changeBoxPosition(notifBoxes, i, moveType, boxesCumulatedHeihgt, createdBox, offset);
167
- }
168
- break;
169
- case "remove":
170
- console.log('remove')
171
- let si = Array.prototype.indexOf.call(notifBoxes, createdBox);
172
- for (let i = si; i >= 0; i--) {
173
- boxesCumulatedHeihgt = this.changeBoxPosition(notifBoxes, i, moveType, boxesCumulatedHeihgt, createdBox, offset);
174
- }
175
- for (let j = (si + 1); j < notifBoxes.length; j++) {
176
- notifBoxes[j].setAttribute('index', j - 1);
177
- console.log((si))
178
- }
179
- break;
180
- default:
181
- break;
182
- }
183
-
184
-
185
- if (boxesCumulatedHeihgt > ((window.innerHeight * 50) / 100)) {
186
- notifBoxes[0].remove();
187
- }
188
- }
189
- changeBoxPosition(notifBoxes, i, moveType, boxesCumulatedHeihgt, createdBox, offset) {
190
- let box = notifBoxes[i];
191
-
192
- if (createdBox != box && box != undefined) {
193
-
194
- if (this.config.y_pos == 'bottom') {
195
- box.style.top = moveType == "add" ? `${box.offsetTop - offset}px` : `${box.offsetTop + offset}px`;
196
- }
197
- if (this.config.y_pos == 'top') {
198
- box.style.top = moveType == "add" ? `${box.offsetTop + offset}px` : `${box.offsetTop - offset}px`;
199
- }
200
- boxesCumulatedHeihgt += box.offsetHeight;
201
- }
202
- return boxesCumulatedHeihgt;
203
- }
204
- get currentColor() {
205
- return this.config.display.colors[this.#type];
206
- }
207
- notify(infos) {
208
- let notif = this.buildBox(infos);
209
- document.querySelector("body").before(notif);
210
-
211
- let pos = this.getPosition({ width: notif.offsetWidth, height: notif.offsetHeight });
212
- notif.style.left = `${pos.x}px`;
213
- notif.style.top = `${pos.y}px`;
214
- this.animProgress(notif);
215
- this.moveDisplayedNotifications(notif)
216
-
217
- var op = 0;
218
- var it = setInterval(function () {
219
- op = op + 0.01;
220
- notif.style.opacity = op;
221
- // console.log(notif)
222
- if (op > 0.9) {
223
- clearInterval(it);
224
- }
225
- }, 1);
226
-
227
- setTimeout(function () {
228
- var op = 1;
229
- var it = setInterval(function () {
230
- op = op - 0.01;
231
- notif.style.opacity = op;
232
- // console.log(notif)
233
- if (op < 0.01) {
234
- notif.remove();
235
- clearInterval(it);
236
- }
237
- }, 1);
238
- }, this.config.duration);
239
- }
240
- buildBox(infos) {
241
- this.configure(infos);
242
- let box = this.mainContainer();
243
- this.config.closer ? box.append(this.dismiss()) : '';
244
- box.append(this.body());
245
- this.config.progress ? box.append(this.progress()) : '';
246
-
247
- return box;
248
- }
249
- container() {
250
- let box = document.createElement('div');
251
- let content = document.createElement('div');
252
-
253
- box.append(content);
254
- return box;
255
- }
256
- mainContainer() {
257
- var notif = document.createElement("div");
258
-
259
- notif.setAttribute("class", "Thesharsol-notifyer");
260
- notif.style.width = `${this.config.display.width}px`;
261
- notif.style.position = 'absolute';
262
- notif.style.opacity = 0;
263
-
264
- notif.style.fontFamily = `${this.config.font.family}`;
265
- notif.style.fontSize = `${this.config.font.size}px`;
266
- notif.style.fontWeight = `${this.config.font.weight}`;
267
-
268
- notif.style.borderRadius = `${this.config.display.radius}px`;
269
- notif.style.backgroundColor = this.config.display.colors[this.#type].bg;
270
- notif.setAttribute('index', document.querySelectorAll('.Thesharsol-notifyer').length)
271
- if (this.config.bordered) {
272
- notif.style.border = `${this.config.borderWidth}px ${this.currentColor.border.type} ${this.currentColor.border.color}`
273
- }
274
-
275
- return notif;
276
- }
277
- body() {
278
- let body = document.createElement('div');
279
- let bodyContent = document.createElement('div');
280
- body.setAttribute('style', 'width:100%')
281
- // bodyContent.setAttribute('style','width:100%')
282
-
283
- bodyContent.style.color = this.config.display.colors[this.#type].color;
284
- bodyContent.style.paddingLeft = `${this.config.display.padding.left}px`;
285
- bodyContent.style.paddingRight = `${this.config.display.padding.right}px`;
286
- bodyContent.style.paddingTop = `${this.config.display.padding.top}px`;
287
- bodyContent.style.paddingBottom = `${this.config.display.padding.bottom}px`;
288
- bodyContent.innerText = this.message;
289
- body.append(bodyContent);
290
- return body;
291
- }
292
- dismiss() {
293
- let dismiss = document.createElement('div');
294
- let dismissContent = document.createElement('button');
295
- // style
296
- dismissContent.classList.add('close-icon');
297
- dismissContent.fontSize = `22px`
298
- dismiss.setAttribute('style', `display:flex;padding:${this.config.display.padding.top}px ${this.config.display.padding.top}px 0px ${this.config.display.padding.top}px;`);
299
- dismissContent.setAttribute('style', 'font-weight:bold;margin-left:auto;background:transparent;border:0px');
300
-
301
- dismiss.append(dismissContent);
302
-
303
- dismissContent.addEventListener('click', () => {
304
- let boxToRemove = dismissContent.parentNode.parentNode;
305
- this.moveDisplayedNotifications(boxToRemove, "remove", parseInt(boxToRemove.getAttribute("index")));
306
- boxToRemove.remove()
307
- })
308
- return dismiss;
309
- }
310
- progress() {
311
- let progress = document.createElement('div');
312
- let progressContent = document.createElement('div');
313
- progress.style.width = '100%';
314
- progress.style.borderRadius = `${this.config.display.radius}px`;
315
- progress.style.overflow = 'hidden'
316
- progress.classList.add('progress');
317
- progressContent.setAttribute('style', 'height:2px;width:0px');
318
-
319
- progressContent.style.minHeight = `${this.config.progressHeight}px`;
320
- progressContent.style.width = '0px';
321
- progressContent.classList.add('content');
322
- progressContent.style.background = `${this.currentColor.progress ? this.currentColor.progress.bg : 'white'}`;
323
- progress.append(progressContent);
324
- return progress;
325
- }
326
-
327
- animProgress(box) {
328
- let progressBar = box.querySelector('.progress');
329
- let progressBoxContent = box.querySelector('.content');
330
-
331
- //
332
- let maxProgressContentWidth = progressBar.offsetWidth;
333
- let increment = maxProgressContentWidth / (this.config.duration / 30);
334
- let width = 0;
335
- let incInt = setInterval(() => {
336
- width += increment;
337
- progressBoxContent.style.width = `${width}px`;
338
-
339
- if (maxProgressContentWidth <= width) {
340
- clearInterval(incInt);
341
- }
342
- }, 30);
343
-
344
- }
345
- }
346
- // alert()
347
- export let theshtify = new Notif();