kempo-testing-framework 1.2.4 → 1.3.3

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.
Files changed (54) hide show
  1. package/.github/copilot-instructions.md +105 -105
  2. package/.github/workflows/publish-npm.yml +29 -0
  3. package/CONTRIBUTING.md +107 -107
  4. package/gui/components/Collapsible.js +54 -54
  5. package/gui/components/Icon.js +151 -151
  6. package/gui/components/Logs.js +73 -73
  7. package/gui/components/SettingCheckbox.js +42 -42
  8. package/gui/components/SettingNumber.js +77 -77
  9. package/gui/components/SettingSelect.js +67 -67
  10. package/gui/components/TestFramework.js +378 -378
  11. package/gui/components/TestSuite.js +181 -181
  12. package/gui/components/TestSummary.js +189 -189
  13. package/gui/components/Theme.js +40 -40
  14. package/gui/components/settingsStore.js +46 -46
  15. package/gui/icons/fail.svg +1 -1
  16. package/gui/icons/pass.svg +1 -1
  17. package/gui/icons/running.svg +1 -1
  18. package/gui/icons/settings.svg +1 -1
  19. package/package.json +6 -1
  20. package/src/browserTestServer.js +115 -115
  21. package/src/cli.js +198 -198
  22. package/src/gui.js +15 -2
  23. package/src/runBrowserTests.js +87 -87
  24. package/src/runTestFiles.js +94 -94
  25. package/src/runTests.js +83 -83
  26. package/src/utils/logLevels.js +7 -7
  27. package/test.html +30 -30
  28. package/tests/Counter.js +33 -33
  29. package/tests/cli-flags.node-test.js +54 -54
  30. package/tests/cli-help.node-test.js +61 -61
  31. package/tests/cli-loglevel.node-test.js +40 -40
  32. package/tests/collapsible.browser-test.js +49 -49
  33. package/tests/counter.browser-test.js +140 -140
  34. package/tests/example.node-test.js +103 -103
  35. package/tests/fibonacci.js +92 -92
  36. package/tests/fibonacci.test.js +285 -285
  37. package/tests/icon.browser-test.js +54 -54
  38. package/tests/logs.browser-test.js +47 -47
  39. package/tests/setting-checkbox.browser-test.js +48 -48
  40. package/tests/setting-number.browser-test.js +54 -54
  41. package/tests/setting-select.browser-test.js +47 -47
  42. package/tests/settings-store.browser-test.js +26 -26
  43. package/tests/src-browserTestServer.node-test.js +47 -47
  44. package/tests/src-cli.node-test.js +32 -32
  45. package/tests/src-findTests.node-test.js +41 -41
  46. package/tests/src-logLevels.node-test.js +29 -29
  47. package/tests/src-runBrowserTests.node-test.js +41 -41
  48. package/tests/src-runTestFiles.node-test.js +42 -42
  49. package/tests/src-runTests.node-test.js +56 -56
  50. package/tests/test-framework.browser-test.js +65 -65
  51. package/tests/test-summary.browser-test.js +78 -78
  52. package/tests/test.browser-test.js +56 -56
  53. package/tests/testfile.browser-test.js +60 -60
  54. package/tests/theme.browser-test.js +38 -38
@@ -1,141 +1,141 @@
1
- import './Counter.js';
2
-
3
- const wait = ms => new Promise(r=>setTimeout(r,ms));
4
-
5
- export const beforeAll = (log) => {
6
- log('Setting up browser test environment for Counter component');
7
- }
8
-
9
- export const beforeEach = (log) => {
10
- log('Setting up test fixture - creating counter element');
11
- const $counter = document.createElement('my-counter');
12
- $counter.id = 'test-counter';
13
- document.body.appendChild($counter);
14
- }
15
-
16
- export const afterEach = (log) => {
17
- log('Cleaning up - removing counter element');
18
- const $counter = document.getElementById('test-counter');
19
- if ($counter) {
20
- $counter.remove();
21
- }
22
- }
23
-
24
- export const afterAll = (log) => {
25
- log('Cleaning up browser test environment');
26
- }
27
-
28
- export default {
29
- 'Counter component should be defined': ({pass, fail, log}) => {
30
- log('Checking if my-counter element is defined');
31
- if (window.customElements.get('my-counter')) {
32
- pass('Counter component is properly registered');
33
- } else {
34
- fail('Counter component is not registered as a custom element');
35
- }
36
- },
37
-
38
- 'Counter should render with initial count of 0': ({pass, fail, log}) => {
39
- const $counter = document.getElementById('test-counter');
40
- log('Checking initial counter value');
41
-
42
- if (!$counter) {
43
- fail('Counter element not found');
44
- return;
45
- }
46
-
47
- // Check shadow DOM content
48
- const countDisplay = $counter.shadowRoot.querySelector('div');
49
- if (countDisplay && countDisplay.textContent.includes('Count: 0')) {
50
- pass('Counter initializes with count of 0');
51
- } else {
52
- fail(`Counter does not show the expected initial value. Found: ${countDisplay?.textContent}`);
53
- }
54
- },
55
-
56
- 'Counter should increment when clicked': async ({pass, fail, log}) => {
57
- const counter = document.getElementById('test-counter');
58
- log('Testing click increment functionality');
59
-
60
- if (!counter) {
61
- fail('Counter element not found');
62
- return;
63
- }
64
-
65
- const button = counter.shadowRoot.querySelector('button');
66
-
67
- // Initial state check
68
- let countDisplay = counter.shadowRoot.querySelector('div');
69
- const initialText = countDisplay.textContent;
70
- log(`Initial state: ${initialText}`);
71
-
72
- // Trigger click event
73
- button.click();
74
-
75
- // Check updated state
76
- countDisplay = counter.shadowRoot.querySelector('div');
77
- const updatedText = countDisplay.textContent;
78
- log(`After click: ${updatedText}`);
79
-
80
- if (updatedText.includes('Count: 1')) {
81
- pass('Counter successfully incremented to 1 after click');
82
- } else {
83
- fail(`Counter did not increment properly. Expected 'Count: 1' but found '${updatedText}'`);
84
- }
85
- },
86
-
87
- 'Counter should increment multiple times': async ({pass, fail, log}) => {
88
- const counter = document.getElementById('test-counter');
89
- log('Testing multiple increments');
90
-
91
- if (!counter) {
92
- fail('Counter element not found');
93
- return;
94
- }
95
-
96
- const button = counter.shadowRoot.querySelector('button');
97
-
98
- // Simply click the button multiple times
99
- for (let i = 0; i < 3; i++) {
100
- button.click();
101
- await wait(100);
102
- log(`After click ${i+1}, counter shows: ${counter.shadowRoot.querySelector('div').textContent}`);
103
- }
104
-
105
- const countDisplay = counter.shadowRoot.querySelector('div');
106
- const countText = countDisplay.textContent;
107
-
108
- if (countText.includes('Count: 3')) {
109
- pass('Counter successfully incremented multiple times to 3');
110
- } else {
111
- fail(`Counter did not increment correctly after multiple clicks. Expected 'Count: 3' but found '${countText}'`);
112
- }
113
- },
114
-
115
- 'Counter should maintain internal state': ({pass, fail, log}) => {
116
- const counter = document.getElementById('test-counter');
117
- log('Testing internal state management');
118
-
119
- if (!counter) {
120
- fail('Counter element not found');
121
- return;
122
- }
123
-
124
- // Check initial internal state
125
- if (counter.count === 0) {
126
- // Manually update the internal state
127
- counter.count = 42;
128
- counter.render();
129
-
130
- // Verify the rendered output matches the internal state
131
- const countDisplay = counter.shadowRoot.querySelector('div');
132
- if (countDisplay.textContent.includes('Count: 42')) {
133
- pass('Counter maintains and displays internal state correctly');
134
- } else {
135
- fail(`Counter does not display the expected state. Expected 'Count: 42' but found '${countDisplay.textContent}'`);
136
- }
137
- } else {
138
- fail(`Initial counter state is not 0, found: ${counter.count}`);
139
- }
140
- }
1
+ import './Counter.js';
2
+
3
+ const wait = ms => new Promise(r=>setTimeout(r,ms));
4
+
5
+ export const beforeAll = (log) => {
6
+ log('Setting up browser test environment for Counter component');
7
+ }
8
+
9
+ export const beforeEach = (log) => {
10
+ log('Setting up test fixture - creating counter element');
11
+ const $counter = document.createElement('my-counter');
12
+ $counter.id = 'test-counter';
13
+ document.body.appendChild($counter);
14
+ }
15
+
16
+ export const afterEach = (log) => {
17
+ log('Cleaning up - removing counter element');
18
+ const $counter = document.getElementById('test-counter');
19
+ if ($counter) {
20
+ $counter.remove();
21
+ }
22
+ }
23
+
24
+ export const afterAll = (log) => {
25
+ log('Cleaning up browser test environment');
26
+ }
27
+
28
+ export default {
29
+ 'Counter component should be defined': ({pass, fail, log}) => {
30
+ log('Checking if my-counter element is defined');
31
+ if (window.customElements.get('my-counter')) {
32
+ pass('Counter component is properly registered');
33
+ } else {
34
+ fail('Counter component is not registered as a custom element');
35
+ }
36
+ },
37
+
38
+ 'Counter should render with initial count of 0': ({pass, fail, log}) => {
39
+ const $counter = document.getElementById('test-counter');
40
+ log('Checking initial counter value');
41
+
42
+ if (!$counter) {
43
+ fail('Counter element not found');
44
+ return;
45
+ }
46
+
47
+ // Check shadow DOM content
48
+ const countDisplay = $counter.shadowRoot.querySelector('div');
49
+ if (countDisplay && countDisplay.textContent.includes('Count: 0')) {
50
+ pass('Counter initializes with count of 0');
51
+ } else {
52
+ fail(`Counter does not show the expected initial value. Found: ${countDisplay?.textContent}`);
53
+ }
54
+ },
55
+
56
+ 'Counter should increment when clicked': async ({pass, fail, log}) => {
57
+ const counter = document.getElementById('test-counter');
58
+ log('Testing click increment functionality');
59
+
60
+ if (!counter) {
61
+ fail('Counter element not found');
62
+ return;
63
+ }
64
+
65
+ const button = counter.shadowRoot.querySelector('button');
66
+
67
+ // Initial state check
68
+ let countDisplay = counter.shadowRoot.querySelector('div');
69
+ const initialText = countDisplay.textContent;
70
+ log(`Initial state: ${initialText}`);
71
+
72
+ // Trigger click event
73
+ button.click();
74
+
75
+ // Check updated state
76
+ countDisplay = counter.shadowRoot.querySelector('div');
77
+ const updatedText = countDisplay.textContent;
78
+ log(`After click: ${updatedText}`);
79
+
80
+ if (updatedText.includes('Count: 1')) {
81
+ pass('Counter successfully incremented to 1 after click');
82
+ } else {
83
+ fail(`Counter did not increment properly. Expected 'Count: 1' but found '${updatedText}'`);
84
+ }
85
+ },
86
+
87
+ 'Counter should increment multiple times': async ({pass, fail, log}) => {
88
+ const counter = document.getElementById('test-counter');
89
+ log('Testing multiple increments');
90
+
91
+ if (!counter) {
92
+ fail('Counter element not found');
93
+ return;
94
+ }
95
+
96
+ const button = counter.shadowRoot.querySelector('button');
97
+
98
+ // Simply click the button multiple times
99
+ for (let i = 0; i < 3; i++) {
100
+ button.click();
101
+ await wait(100);
102
+ log(`After click ${i+1}, counter shows: ${counter.shadowRoot.querySelector('div').textContent}`);
103
+ }
104
+
105
+ const countDisplay = counter.shadowRoot.querySelector('div');
106
+ const countText = countDisplay.textContent;
107
+
108
+ if (countText.includes('Count: 3')) {
109
+ pass('Counter successfully incremented multiple times to 3');
110
+ } else {
111
+ fail(`Counter did not increment correctly after multiple clicks. Expected 'Count: 3' but found '${countText}'`);
112
+ }
113
+ },
114
+
115
+ 'Counter should maintain internal state': ({pass, fail, log}) => {
116
+ const counter = document.getElementById('test-counter');
117
+ log('Testing internal state management');
118
+
119
+ if (!counter) {
120
+ fail('Counter element not found');
121
+ return;
122
+ }
123
+
124
+ // Check initial internal state
125
+ if (counter.count === 0) {
126
+ // Manually update the internal state
127
+ counter.count = 42;
128
+ counter.render();
129
+
130
+ // Verify the rendered output matches the internal state
131
+ const countDisplay = counter.shadowRoot.querySelector('div');
132
+ if (countDisplay.textContent.includes('Count: 42')) {
133
+ pass('Counter maintains and displays internal state correctly');
134
+ } else {
135
+ fail(`Counter does not display the expected state. Expected 'Count: 42' but found '${countDisplay.textContent}'`);
136
+ }
137
+ } else {
138
+ fail(`Initial counter state is not 0, found: ${counter.count}`);
139
+ }
140
+ }
141
141
  };
@@ -1,103 +1,103 @@
1
- // Example Node test file for Kempo Testing Framework
2
- // This demonstrates the basic structure and features
3
-
4
- /* Export the optional lifecycle callbacks */
5
- export const beforeAll = async (log) => {
6
- log('Setting up Node test environment...');
7
- // Setup that runs once before all tests
8
- }
9
-
10
- export const beforeEach = async (log) => {
11
- log('Setting up for each test');
12
- // Setup that runs before each individual test
13
- }
14
-
15
- export const afterEach = async (log) => {
16
- log('Cleaning up after each test');
17
- // Cleanup that runs after each individual test
18
- }
19
-
20
- export const afterAll = async (log) => {
21
- log('Cleaning up Node test environment...');
22
- // Cleanup that runs once after all tests
23
- }
24
-
25
- /* Export the Tests */
26
- export default {
27
- 'should handle basic string operations': async ({pass, fail, log}) => {
28
- const input = 'hello world';
29
- const expected = 'HELLO WORLD';
30
- const result = input.toUpperCase();
31
-
32
- log(`Testing string conversion: "${input}" -> "${result}"`);
33
-
34
- if (result === expected) {
35
- log('✓ String conversion works correctly');
36
- pass('String uppercase conversion test passed');
37
- } else {
38
- fail(`Expected "${expected}", got "${result}"`);
39
- }
40
- },
41
-
42
- 'should validate array operations': async ({pass, fail, log}) => {
43
- const numbers = [1, 2, 3, 4, 5];
44
- const doubled = numbers.map(n => n * 2);
45
- const expected = [2, 4, 6, 8, 10];
46
-
47
- log(`Testing array mapping: [${numbers}] -> [${doubled}]`);
48
-
49
- if (JSON.stringify(doubled) === JSON.stringify(expected)) {
50
- log('✓ Array mapping works correctly');
51
- pass('Array mapping test passed');
52
- } else {
53
- fail(`Expected [${expected}], got [${doubled}]`);
54
- }
55
- },
56
-
57
- 'should handle async operations': async ({pass, fail, log}) => {
58
- log('Testing async operation...');
59
-
60
- const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
61
- const start = Date.now();
62
-
63
- await delay(100);
64
-
65
- const elapsed = Date.now() - start;
66
- log(`Async delay completed in ${elapsed}ms`);
67
-
68
- if (elapsed >= 100 && elapsed < 200) {
69
- log('✓ Async operation timing is correct');
70
- pass('Async operation test passed');
71
- } else {
72
- fail(`Expected delay of ~100ms, got ${elapsed}ms`);
73
- }
74
- },
75
-
76
- 'should handle error conditions': async ({pass, fail, log}) => {
77
- log('Testing error handling...');
78
-
79
- try {
80
- JSON.parse('invalid json');
81
- fail('Should have thrown an error for invalid JSON');
82
- } catch (error) {
83
- log(`✓ Caught expected error: ${error.message}`);
84
- pass('Error handling test passed');
85
- }
86
- },
87
-
88
- 'should work with Node.js specific features': async ({pass, fail, log}) => {
89
- // Test Node.js specific functionality
90
- const process_exists = typeof process !== 'undefined';
91
- const has_global = typeof global !== 'undefined';
92
-
93
- log(`Process object exists: ${process_exists}`);
94
- log(`Global object available: ${has_global}`);
95
-
96
- if (process_exists && has_global) {
97
- log('✓ Node.js environment detected correctly');
98
- pass('Node.js environment test passed');
99
- } else {
100
- fail('Node.js environment features not available');
101
- }
102
- }
103
- };
1
+ // Example Node test file for Kempo Testing Framework
2
+ // This demonstrates the basic structure and features
3
+
4
+ /* Export the optional lifecycle callbacks */
5
+ export const beforeAll = async (log) => {
6
+ log('Setting up Node test environment...');
7
+ // Setup that runs once before all tests
8
+ }
9
+
10
+ export const beforeEach = async (log) => {
11
+ log('Setting up for each test');
12
+ // Setup that runs before each individual test
13
+ }
14
+
15
+ export const afterEach = async (log) => {
16
+ log('Cleaning up after each test');
17
+ // Cleanup that runs after each individual test
18
+ }
19
+
20
+ export const afterAll = async (log) => {
21
+ log('Cleaning up Node test environment...');
22
+ // Cleanup that runs once after all tests
23
+ }
24
+
25
+ /* Export the Tests */
26
+ export default {
27
+ 'should handle basic string operations': async ({pass, fail, log}) => {
28
+ const input = 'hello world';
29
+ const expected = 'HELLO WORLD';
30
+ const result = input.toUpperCase();
31
+
32
+ log(`Testing string conversion: "${input}" -> "${result}"`);
33
+
34
+ if (result === expected) {
35
+ log('✓ String conversion works correctly');
36
+ pass('String uppercase conversion test passed');
37
+ } else {
38
+ fail(`Expected "${expected}", got "${result}"`);
39
+ }
40
+ },
41
+
42
+ 'should validate array operations': async ({pass, fail, log}) => {
43
+ const numbers = [1, 2, 3, 4, 5];
44
+ const doubled = numbers.map(n => n * 2);
45
+ const expected = [2, 4, 6, 8, 10];
46
+
47
+ log(`Testing array mapping: [${numbers}] -> [${doubled}]`);
48
+
49
+ if (JSON.stringify(doubled) === JSON.stringify(expected)) {
50
+ log('✓ Array mapping works correctly');
51
+ pass('Array mapping test passed');
52
+ } else {
53
+ fail(`Expected [${expected}], got [${doubled}]`);
54
+ }
55
+ },
56
+
57
+ 'should handle async operations': async ({pass, fail, log}) => {
58
+ log('Testing async operation...');
59
+
60
+ const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
61
+ const start = Date.now();
62
+
63
+ await delay(100);
64
+
65
+ const elapsed = Date.now() - start;
66
+ log(`Async delay completed in ${elapsed}ms`);
67
+
68
+ if (elapsed >= 100 && elapsed < 200) {
69
+ log('✓ Async operation timing is correct');
70
+ pass('Async operation test passed');
71
+ } else {
72
+ fail(`Expected delay of ~100ms, got ${elapsed}ms`);
73
+ }
74
+ },
75
+
76
+ 'should handle error conditions': async ({pass, fail, log}) => {
77
+ log('Testing error handling...');
78
+
79
+ try {
80
+ JSON.parse('invalid json');
81
+ fail('Should have thrown an error for invalid JSON');
82
+ } catch (error) {
83
+ log(`✓ Caught expected error: ${error.message}`);
84
+ pass('Error handling test passed');
85
+ }
86
+ },
87
+
88
+ 'should work with Node.js specific features': async ({pass, fail, log}) => {
89
+ // Test Node.js specific functionality
90
+ const process_exists = typeof process !== 'undefined';
91
+ const has_global = typeof global !== 'undefined';
92
+
93
+ log(`Process object exists: ${process_exists}`);
94
+ log(`Global object available: ${has_global}`);
95
+
96
+ if (process_exists && has_global) {
97
+ log('✓ Node.js environment detected correctly');
98
+ pass('Node.js environment test passed');
99
+ } else {
100
+ fail('Node.js environment features not available');
101
+ }
102
+ }
103
+ };
@@ -1,92 +1,92 @@
1
- /**
2
- * Fibonacci sequence utility functions
3
- * Works in both Node.js and browser environments
4
- */
5
-
6
- /**
7
- * Generate the nth Fibonacci number (0-indexed)
8
- * @param {number} n - The position in the sequence (0-indexed)
9
- * @returns {number} The Fibonacci number at position n
10
- * @throws {Error} If n is negative or not a number
11
- */
12
- const fibonacci = (n) => {
13
- if(typeof n !== 'number' || !Number.isInteger(n)) {
14
- throw new Error('Input must be a non-negative integer');
15
- }
16
-
17
- if(n < 0) {
18
- throw new Error('Input must be a non-negative integer');
19
- }
20
-
21
- if(n === 0) return 0;
22
- if(n === 1) return 1;
23
-
24
- let a = 0;
25
- let b = 1;
26
-
27
- for(let i = 2; i <= n; i++) {
28
- const temp = a + b;
29
- a = b;
30
- b = temp;
31
- }
32
-
33
- return b;
34
- };
35
-
36
- /**
37
- * Generate a Fibonacci sequence up to n terms
38
- * @param {number} count - Number of terms to generate
39
- * @returns {number[]} Array containing the Fibonacci sequence
40
- * @throws {Error} If count is negative or not a number
41
- */
42
- const fibonacciSequence = (count) => {
43
- if(typeof count !== 'number' || !Number.isInteger(count)) {
44
- throw new Error('Count must be a non-negative integer');
45
- }
46
-
47
- if(count < 0) {
48
- throw new Error('Count must be a non-negative integer');
49
- }
50
-
51
- if(count === 0) return [];
52
- if(count === 1) return [0];
53
- if(count === 2) return [0, 1];
54
-
55
- const sequence = [0, 1];
56
-
57
- for(let i = 2; i < count; i++) {
58
- sequence.push(sequence[i - 1] + sequence[i - 2]);
59
- }
60
-
61
- return sequence;
62
- };
63
-
64
- /**
65
- * Check if a number is a Fibonacci number
66
- * @param {number} num - The number to check
67
- * @returns {boolean} True if the number is in the Fibonacci sequence
68
- */
69
- const isFibonacci = (num) => {
70
- if(typeof num !== 'number' || !Number.isInteger(num) || num < 0) {
71
- return false;
72
- }
73
-
74
- if(num === 0 || num === 1) return true;
75
-
76
- let a = 0;
77
- let b = 1;
78
-
79
- while (b < num) {
80
- const temp = a + b;
81
- a = b;
82
- b = temp;
83
- }
84
-
85
- return b === num;
86
- };
87
-
88
- export default {
89
- fibonacci,
90
- fibonacciSequence,
91
- isFibonacci
92
- };
1
+ /**
2
+ * Fibonacci sequence utility functions
3
+ * Works in both Node.js and browser environments
4
+ */
5
+
6
+ /**
7
+ * Generate the nth Fibonacci number (0-indexed)
8
+ * @param {number} n - The position in the sequence (0-indexed)
9
+ * @returns {number} The Fibonacci number at position n
10
+ * @throws {Error} If n is negative or not a number
11
+ */
12
+ const fibonacci = (n) => {
13
+ if(typeof n !== 'number' || !Number.isInteger(n)) {
14
+ throw new Error('Input must be a non-negative integer');
15
+ }
16
+
17
+ if(n < 0) {
18
+ throw new Error('Input must be a non-negative integer');
19
+ }
20
+
21
+ if(n === 0) return 0;
22
+ if(n === 1) return 1;
23
+
24
+ let a = 0;
25
+ let b = 1;
26
+
27
+ for(let i = 2; i <= n; i++) {
28
+ const temp = a + b;
29
+ a = b;
30
+ b = temp;
31
+ }
32
+
33
+ return b;
34
+ };
35
+
36
+ /**
37
+ * Generate a Fibonacci sequence up to n terms
38
+ * @param {number} count - Number of terms to generate
39
+ * @returns {number[]} Array containing the Fibonacci sequence
40
+ * @throws {Error} If count is negative or not a number
41
+ */
42
+ const fibonacciSequence = (count) => {
43
+ if(typeof count !== 'number' || !Number.isInteger(count)) {
44
+ throw new Error('Count must be a non-negative integer');
45
+ }
46
+
47
+ if(count < 0) {
48
+ throw new Error('Count must be a non-negative integer');
49
+ }
50
+
51
+ if(count === 0) return [];
52
+ if(count === 1) return [0];
53
+ if(count === 2) return [0, 1];
54
+
55
+ const sequence = [0, 1];
56
+
57
+ for(let i = 2; i < count; i++) {
58
+ sequence.push(sequence[i - 1] + sequence[i - 2]);
59
+ }
60
+
61
+ return sequence;
62
+ };
63
+
64
+ /**
65
+ * Check if a number is a Fibonacci number
66
+ * @param {number} num - The number to check
67
+ * @returns {boolean} True if the number is in the Fibonacci sequence
68
+ */
69
+ const isFibonacci = (num) => {
70
+ if(typeof num !== 'number' || !Number.isInteger(num) || num < 0) {
71
+ return false;
72
+ }
73
+
74
+ if(num === 0 || num === 1) return true;
75
+
76
+ let a = 0;
77
+ let b = 1;
78
+
79
+ while (b < num) {
80
+ const temp = a + b;
81
+ a = b;
82
+ b = temp;
83
+ }
84
+
85
+ return b === num;
86
+ };
87
+
88
+ export default {
89
+ fibonacci,
90
+ fibonacciSequence,
91
+ isFibonacci
92
+ };