@vonaffenfels/contentful-teasermanager 1.2.30 → 1.2.31
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/jest.config.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
testEnvironment: 'node',
|
|
3
|
+
testMatch: ['**/__tests__/**/*.js', '**/?(*.)+(spec|test).js'],
|
|
4
|
+
collectCoverageFrom: ['src/**/*.js', '!src/**/*.test.js', '!src/**/*.spec.js'],
|
|
5
|
+
transform: {
|
|
6
|
+
'^.+\\.js$': ['babel-jest', {presets: ['@babel/preset-env']}],
|
|
7
|
+
},
|
|
8
|
+
};
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vonaffenfels/contentful-teasermanager",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.31",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"prepublish": "yarn run build",
|
|
7
7
|
"dev": "yarn run start",
|
|
8
|
-
"test": "
|
|
8
|
+
"test": "jest",
|
|
9
9
|
"build": "echo \"Don't build this module!\"",
|
|
10
10
|
"link": "yarn link",
|
|
11
11
|
"start": "webpack serve --config webpack.config.dev.js"
|
|
@@ -67,6 +67,7 @@
|
|
|
67
67
|
"@dnd-kit/core": "^6.3.1",
|
|
68
68
|
"@svgr/webpack": "^5.5.0",
|
|
69
69
|
"@tanstack/react-query": "^4",
|
|
70
|
+
"babel-jest": "^30.2.0",
|
|
70
71
|
"babel-loader": "^8.2.2",
|
|
71
72
|
"classnames": "^2.3.2",
|
|
72
73
|
"clean-webpack-plugin": "^3.0.0",
|
|
@@ -80,6 +81,7 @@
|
|
|
80
81
|
"dotenv-webpack": "^7.0.2",
|
|
81
82
|
"file-loader": "^6.2.0",
|
|
82
83
|
"html-webpack-plugin": "^5.3.1",
|
|
84
|
+
"jest": "^30.2.0",
|
|
83
85
|
"mini-css-extract-plugin": "^1.5.0",
|
|
84
86
|
"postcss": "^8",
|
|
85
87
|
"postcss-loader": "^7",
|
|
@@ -102,7 +104,7 @@
|
|
|
102
104
|
"contentful-resolve-response": "^1.9.2",
|
|
103
105
|
"webpack": "5.88.2"
|
|
104
106
|
},
|
|
105
|
-
"gitHead": "
|
|
107
|
+
"gitHead": "a7f6706ff699696493a6039fe2ac0ef500993453",
|
|
106
108
|
"publishConfig": {
|
|
107
109
|
"access": "public"
|
|
108
110
|
}
|
package/src/queryFromLogic.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import {getGermanTimezoneOffset} from "./utils/germanTimezoneOffset";
|
|
2
|
+
|
|
1
3
|
export const queryFromLogic = (logic) => {
|
|
2
4
|
const orTags = logic.tags.filter(v => v.type === "or").map(v => v.id);
|
|
3
5
|
const andTags = logic.tags.filter(v => v.type === "and").map(v => v.id);
|
|
4
6
|
const excludeTags = logic.tags.filter(v => v.type === "exclude").map(v => v.id);
|
|
5
7
|
const timeZoneOffsetInHours = new Date().getTimezoneOffset() / 60;
|
|
6
|
-
const differenceToGermanTime = timeZoneOffsetInHours +
|
|
8
|
+
const differenceToGermanTime = timeZoneOffsetInHours + getGermanTimezoneOffset();
|
|
7
9
|
const hours = [...(logic.timepoints || [])].map(v => parseInt(v)).sort((a, b) => a - b);
|
|
8
10
|
let nearestPassedTime = hours.reverse().find(v => v <= new Date().getHours() + differenceToGermanTime);
|
|
9
11
|
const logicQuery = {"fields.portal": logic.project};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import {queryFromLogic} from './queryFromLogic';
|
|
2
|
+
|
|
3
|
+
// Mock the germanTimezoneOffset utility
|
|
4
|
+
jest.mock('./utils/germanTimezoneOffset', () => ({
|
|
5
|
+
getGermanTimezoneOffset: jest.fn(),
|
|
6
|
+
}));
|
|
7
|
+
|
|
8
|
+
const {getGermanTimezoneOffset} = require('./utils/germanTimezoneOffset');
|
|
9
|
+
|
|
10
|
+
describe('queryFromLogic', () => {
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
jest.clearAllMocks();
|
|
13
|
+
// Mock Date to fixed value for consistent testing
|
|
14
|
+
jest.useFakeTimers();
|
|
15
|
+
jest.setSystemTime(new Date('2025-07-15T14:30:00Z')); // 14:30 UTC
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
jest.useRealTimers();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should use dynamic timezone offset in summer (CEST = UTC+2)', () => {
|
|
23
|
+
getGermanTimezoneOffset.mockReturnValue(2);
|
|
24
|
+
|
|
25
|
+
const logic = {
|
|
26
|
+
project: 'test-project',
|
|
27
|
+
tags: [],
|
|
28
|
+
timepoints: ['12', '14', '16'],
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
queryFromLogic(logic);
|
|
32
|
+
|
|
33
|
+
// Verify that getGermanTimezoneOffset was called
|
|
34
|
+
expect(getGermanTimezoneOffset).toHaveBeenCalled();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should use dynamic timezone offset in winter (CET = UTC+1)', () => {
|
|
38
|
+
jest.setSystemTime(new Date('2025-01-15T14:30:00Z')); // 14:30 UTC in winter
|
|
39
|
+
getGermanTimezoneOffset.mockReturnValue(1);
|
|
40
|
+
|
|
41
|
+
const logic = {
|
|
42
|
+
project: 'test-project',
|
|
43
|
+
tags: [],
|
|
44
|
+
timepoints: ['12', '14', '16'],
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
queryFromLogic(logic);
|
|
48
|
+
|
|
49
|
+
// Verify that getGermanTimezoneOffset was called
|
|
50
|
+
expect(getGermanTimezoneOffset).toHaveBeenCalled();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should handle OR tags correctly', () => {
|
|
54
|
+
getGermanTimezoneOffset.mockReturnValue(2);
|
|
55
|
+
|
|
56
|
+
const logic = {
|
|
57
|
+
project: 'test-project',
|
|
58
|
+
tags: [
|
|
59
|
+
{type: 'or', id: 'tag1'},
|
|
60
|
+
{type: 'or', id: 'tag2'},
|
|
61
|
+
],
|
|
62
|
+
timepoints: [],
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const result = queryFromLogic(logic);
|
|
66
|
+
|
|
67
|
+
expect(result['fields.tags.sys.id[in]']).toBe('tag1,tag2');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should handle AND tags correctly', () => {
|
|
71
|
+
getGermanTimezoneOffset.mockReturnValue(2);
|
|
72
|
+
|
|
73
|
+
const logic = {
|
|
74
|
+
project: 'test-project',
|
|
75
|
+
tags: [
|
|
76
|
+
{type: 'and', id: 'tag1'},
|
|
77
|
+
{type: 'and', id: 'tag2'},
|
|
78
|
+
],
|
|
79
|
+
timepoints: [],
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const result = queryFromLogic(logic);
|
|
83
|
+
|
|
84
|
+
expect(result['fields.tags.sys.id[all]']).toBe('tag1,tag2');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should handle exclude tags correctly', () => {
|
|
88
|
+
getGermanTimezoneOffset.mockReturnValue(2);
|
|
89
|
+
|
|
90
|
+
const logic = {
|
|
91
|
+
project: 'test-project',
|
|
92
|
+
tags: [
|
|
93
|
+
{type: 'exclude', id: 'tag1'},
|
|
94
|
+
{type: 'exclude', id: 'tag2'},
|
|
95
|
+
],
|
|
96
|
+
timepoints: [],
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const result = queryFromLogic(logic);
|
|
100
|
+
|
|
101
|
+
expect(result['fields.tags.sys.id[nin]']).toBe('tag1,tag2');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should always include project in query', () => {
|
|
105
|
+
getGermanTimezoneOffset.mockReturnValue(2);
|
|
106
|
+
|
|
107
|
+
const logic = {
|
|
108
|
+
project: 'my-portal',
|
|
109
|
+
tags: [],
|
|
110
|
+
timepoints: [],
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const result = queryFromLogic(logic);
|
|
114
|
+
|
|
115
|
+
expect(result['fields.portal']).toBe('my-portal');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should not add date filter when no timepoints provided', () => {
|
|
119
|
+
getGermanTimezoneOffset.mockReturnValue(2);
|
|
120
|
+
|
|
121
|
+
const logic = {
|
|
122
|
+
project: 'test-project',
|
|
123
|
+
tags: [],
|
|
124
|
+
timepoints: [],
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const result = queryFromLogic(logic);
|
|
128
|
+
|
|
129
|
+
expect(result['fields.date[lte]']).toBeUndefined();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should use first timepoint when current time is before all timepoints', () => {
|
|
133
|
+
jest.setSystemTime(new Date('2025-07-15T08:00:00Z')); // 08:00 UTC = 10:00 CEST
|
|
134
|
+
getGermanTimezoneOffset.mockReturnValue(2);
|
|
135
|
+
|
|
136
|
+
const logic = {
|
|
137
|
+
project: 'test-project',
|
|
138
|
+
tags: [],
|
|
139
|
+
timepoints: ['12', '14', '16'],
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const result = queryFromLogic(logic);
|
|
143
|
+
|
|
144
|
+
expect(result['fields.date[lte]']).toBeDefined();
|
|
145
|
+
// Verify timezone function was called
|
|
146
|
+
expect(getGermanTimezoneOffset).toHaveBeenCalled();
|
|
147
|
+
});
|
|
148
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculate the timezone offset in hours between UTC and Europe/Berlin timezone.
|
|
3
|
+
* Automatically handles Daylight Saving Time (DST) transitions:
|
|
4
|
+
* - CET (Central European Time): UTC+1 (Last Sunday October to Last Sunday March)
|
|
5
|
+
* - CEST (Central European Summer Time): UTC+2 (Last Sunday March to Last Sunday October)
|
|
6
|
+
*
|
|
7
|
+
* @param {Date} date - The date for which to calculate the offset (default: current date)
|
|
8
|
+
* @returns {number} The offset in hours (1 for CET, 2 for CEST)
|
|
9
|
+
*/
|
|
10
|
+
export const getGermanTimezoneOffset = (date = new Date()) => {
|
|
11
|
+
// Get timezone offset for Europe/Berlin using Intl API
|
|
12
|
+
const formatter = new Intl.DateTimeFormat('en-US', {
|
|
13
|
+
timeZone: 'Europe/Berlin',
|
|
14
|
+
timeZoneName: 'shortOffset',
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const parts = formatter.formatToParts(date);
|
|
18
|
+
const offsetPart = parts.find(part => part.type === 'timeZoneName');
|
|
19
|
+
|
|
20
|
+
if (!offsetPart) {
|
|
21
|
+
// Fallback: should not happen with modern Node.js
|
|
22
|
+
throw new Error('Unable to determine timezone offset for Europe/Berlin');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Parse offset string (format: "GMT+1" or "GMT+2")
|
|
26
|
+
const offsetMatch = offsetPart.value.match(/GMT([+-])(\d+)/);
|
|
27
|
+
if (!offsetMatch) {
|
|
28
|
+
throw new Error(`Unexpected offset format: ${offsetPart.value}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const sign = offsetMatch[1] === '+' ? 1 : -1;
|
|
32
|
+
const hours = parseInt(offsetMatch[2], 10);
|
|
33
|
+
|
|
34
|
+
return sign * hours;
|
|
35
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import {getGermanTimezoneOffset} from './germanTimezoneOffset';
|
|
2
|
+
|
|
3
|
+
describe('getGermanTimezoneOffset', () => {
|
|
4
|
+
it('should return UTC+1 for winter date (CET)', () => {
|
|
5
|
+
// January 15, 2025 - during Central European Time (winter)
|
|
6
|
+
const winterDate = new Date('2025-01-15T12:00:00Z');
|
|
7
|
+
const offset = getGermanTimezoneOffset(winterDate);
|
|
8
|
+
expect(offset).toBe(1);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('should return UTC+2 for summer date (CEST)', () => {
|
|
12
|
+
// July 15, 2025 - during Central European Summer Time
|
|
13
|
+
const summerDate = new Date('2025-07-15T12:00:00Z');
|
|
14
|
+
const offset = getGermanTimezoneOffset(summerDate);
|
|
15
|
+
expect(offset).toBe(2);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should return UTC+1 for early spring date (before DST)', () => {
|
|
19
|
+
// March 1, 2025 - before DST transition
|
|
20
|
+
const earlySpringDate = new Date('2025-03-01T12:00:00Z');
|
|
21
|
+
const offset = getGermanTimezoneOffset(earlySpringDate);
|
|
22
|
+
expect(offset).toBe(1);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should return UTC+2 for late spring date (after DST)', () => {
|
|
26
|
+
// April 15, 2025 - after DST transition
|
|
27
|
+
const lateSpringDate = new Date('2025-04-15T12:00:00Z');
|
|
28
|
+
const offset = getGermanTimezoneOffset(lateSpringDate);
|
|
29
|
+
expect(offset).toBe(2);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should return UTC+2 for early autumn date (before DST ends)', () => {
|
|
33
|
+
// September 15, 2025 - still in summer time
|
|
34
|
+
const earlyAutumnDate = new Date('2025-09-15T12:00:00Z');
|
|
35
|
+
const offset = getGermanTimezoneOffset(earlyAutumnDate);
|
|
36
|
+
expect(offset).toBe(2);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should return UTC+1 for late autumn date (after DST ends)', () => {
|
|
40
|
+
// November 15, 2025 - back to winter time
|
|
41
|
+
const lateAutumnDate = new Date('2025-11-15T12:00:00Z');
|
|
42
|
+
const offset = getGermanTimezoneOffset(lateAutumnDate);
|
|
43
|
+
expect(offset).toBe(1);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should use current date when no argument provided', () => {
|
|
47
|
+
const offset = getGermanTimezoneOffset();
|
|
48
|
+
// Offset should be either 1 or 2 depending on current date
|
|
49
|
+
expect([1, 2]).toContain(offset);
|
|
50
|
+
});
|
|
51
|
+
});
|