es6-fuzz 4.0.1 → 5.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.
- package/Readme.md +2 -3
- package/docs/CHANGELOG.md +26 -16
- package/jsdoc.json +0 -1
- package/lib/curve/grade.js +7 -0
- package/lib/curve/reverse-grade.js +6 -1
- package/lib/curve/sigmoid.js +13 -7
- package/lib/curve/trapezoid.js +25 -2
- package/lib/curve/triangle.js +27 -0
- package/lib/logic.js +0 -2
- package/package.json +6 -6
- package/test/bug-74-test.js +100 -0
- package/test/curve/constant.js +15 -0
- package/test/{shape-fuzzy-function-test.js → curve/fuzzy-function.js} +2 -1
- package/test/{shape-grade-test.js → curve/grade.js} +2 -1
- package/test/{shape-reverse-grade-test.js → curve/reverse-grade.js} +2 -1
- package/test/{shape-test.js → curve/shape.js} +2 -1
- package/test/curve/sigmoid.js +93 -0
- package/test/curve/trapezoid.js +71 -0
- package/test/{shape-triangle-test.js → curve/triangle.js} +2 -1
- package/test/edge-cases.js +244 -0
- package/test/error-handling.js +229 -0
- package/test/example-test.js +1 -0
- package/test/init-called.test.js +1 -0
- package/test/integration.js +343 -0
- package/test/js-bool.test.js +2 -1
- package/test/logic-test.js +1 -0
- package/test/constant-test.js +0 -13
- package/test/shape-sigmoid-test.js +0 -37
- package/test/shape-trapezoid-test.js +0 -25
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const { describe, it } = require('node:test');
|
|
3
|
+
const assert = require('assert');
|
|
4
|
+
const Triangle = require('../lib/curve/triangle');
|
|
5
|
+
const Trapezoid = require('../lib/curve/trapezoid');
|
|
6
|
+
const Grade = require('../lib/curve/grade');
|
|
7
|
+
const ReverseGrade = require('../lib/curve/reverse-grade');
|
|
8
|
+
const Sigmoid = require('../lib/curve/sigmoid');
|
|
9
|
+
const Constant = require('../lib/curve/constant');
|
|
10
|
+
const FuzzyFunction = require('../lib/curve/fuzzy-function');
|
|
11
|
+
|
|
12
|
+
describe('Edge Cases', function() {
|
|
13
|
+
describe('Triangle degenerate cases', function() {
|
|
14
|
+
it('should handle triangle with all points equal (x0=x1=x2)', function() {
|
|
15
|
+
const triangle = new Triangle(5, 5, 5);
|
|
16
|
+
assert.equal(triangle.fuzzify(4.9), 0);
|
|
17
|
+
assert.equal(triangle.fuzzify(5), 1);
|
|
18
|
+
assert.equal(triangle.fuzzify(5.1), 0);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should handle triangle with x0=x1 (right angle left)', function() {
|
|
22
|
+
const triangle = new Triangle(5, 5, 10);
|
|
23
|
+
assert.equal(triangle.fuzzify(4.9), 0);
|
|
24
|
+
assert.equal(triangle.fuzzify(5), 1);
|
|
25
|
+
assert.equal(triangle.fuzzify(7.5), 0.5);
|
|
26
|
+
assert.equal(triangle.fuzzify(10), 0);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should handle triangle with x1=x2 (right angle right)', function() {
|
|
30
|
+
const triangle = new Triangle(0, 5, 5);
|
|
31
|
+
assert.equal(triangle.fuzzify(0), 0);
|
|
32
|
+
assert.equal(triangle.fuzzify(2.5), 0.5);
|
|
33
|
+
assert.equal(triangle.fuzzify(5), 1);
|
|
34
|
+
assert.equal(triangle.fuzzify(5.1), 0);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('Trapezoid special cases', function() {
|
|
39
|
+
it('should handle square trapezoid (x1=x2)', function() {
|
|
40
|
+
const trapezoid = new Trapezoid(0, 5, 5, 10);
|
|
41
|
+
assert.equal(trapezoid.fuzzify(-1), 0);
|
|
42
|
+
assert.equal(trapezoid.fuzzify(0), 0);
|
|
43
|
+
assert.equal(trapezoid.fuzzify(2.5), 0.5);
|
|
44
|
+
assert.equal(trapezoid.fuzzify(5), 1);
|
|
45
|
+
assert.equal(trapezoid.fuzzify(7.5), 0.5);
|
|
46
|
+
assert.equal(trapezoid.fuzzify(10), 0);
|
|
47
|
+
assert.equal(trapezoid.fuzzify(11), 0);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should handle degenerate trapezoid (x0=x1 and x2=x3)', function() {
|
|
51
|
+
const trapezoid = new Trapezoid(5, 5, 10, 10);
|
|
52
|
+
assert.equal(trapezoid.fuzzify(4.9), 0);
|
|
53
|
+
assert.equal(trapezoid.fuzzify(5), 1);
|
|
54
|
+
assert.equal(trapezoid.fuzzify(7.5), 1);
|
|
55
|
+
assert.equal(trapezoid.fuzzify(10), 1);
|
|
56
|
+
assert.equal(trapezoid.fuzzify(10.1), 0);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should handle very narrow trapezoid (x1 and x2 very close)', function() {
|
|
60
|
+
const trapezoid = new Trapezoid(0, 4.999, 5.001, 10);
|
|
61
|
+
assert.equal(trapezoid.fuzzify(5), 1);
|
|
62
|
+
assert(Math.abs(trapezoid.fuzzify(4.999) - 1) < 0.01);
|
|
63
|
+
assert(Math.abs(trapezoid.fuzzify(5.001) - 1) < 0.01);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should handle all points equal (degenerate to point)', function() {
|
|
67
|
+
const trapezoid = new Trapezoid(5, 5, 5, 5);
|
|
68
|
+
assert.equal(trapezoid.fuzzify(4.9), 0);
|
|
69
|
+
assert.equal(trapezoid.fuzzify(5), 1);
|
|
70
|
+
assert.equal(trapezoid.fuzzify(5.1), 0);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('Grade edge cases', function() {
|
|
75
|
+
it('should handle vertical grade (x0=x1)', function() {
|
|
76
|
+
const grade = new Grade(5, 5);
|
|
77
|
+
assert.equal(grade.fuzzify(4.9), 0);
|
|
78
|
+
assert.equal(grade.fuzzify(5), 1);
|
|
79
|
+
assert.equal(grade.fuzzify(5.1), 1);
|
|
80
|
+
assert.equal(grade.fuzzify(100), 1);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should handle very steep grade', function() {
|
|
84
|
+
const grade = new Grade(5, 5.0001);
|
|
85
|
+
assert.equal(grade.fuzzify(4.9), 0);
|
|
86
|
+
assert(grade.fuzzify(5.00005) > 0.4);
|
|
87
|
+
assert.equal(grade.fuzzify(5.1), 1);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('ReverseGrade edge cases', function() {
|
|
92
|
+
it('should handle vertical reverse grade (x0=x1)', function() {
|
|
93
|
+
const reverseGrade = new ReverseGrade(5, 5);
|
|
94
|
+
assert.equal(reverseGrade.fuzzify(4.9), 1);
|
|
95
|
+
assert.equal(reverseGrade.fuzzify(5), 1);
|
|
96
|
+
assert.equal(reverseGrade.fuzzify(5.1), 0);
|
|
97
|
+
assert.equal(reverseGrade.fuzzify(100), 0);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should handle very steep reverse grade', function() {
|
|
101
|
+
const reverseGrade = new ReverseGrade(5, 5.0001);
|
|
102
|
+
assert.equal(reverseGrade.fuzzify(4.9), 1);
|
|
103
|
+
assert(reverseGrade.fuzzify(5.00005) < 0.6);
|
|
104
|
+
assert.equal(reverseGrade.fuzzify(5.1), 0);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe('Sigmoid edge cases', function() {
|
|
109
|
+
it('should handle zero slope (becomes step function)', function() {
|
|
110
|
+
const sigmoid = new Sigmoid(5, 0);
|
|
111
|
+
// With zero slope, sigmoid becomes a step function
|
|
112
|
+
assert.equal(sigmoid.fuzzify(4.9), 0);
|
|
113
|
+
assert.equal(sigmoid.fuzzify(5), 0.5);
|
|
114
|
+
assert.equal(sigmoid.fuzzify(5.1), 1);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should handle very small slope (near step function)', function() {
|
|
118
|
+
const sigmoid = new Sigmoid(5, 0.0001);
|
|
119
|
+
assert(sigmoid.fuzzify(4.99) < 0.01);
|
|
120
|
+
assert(sigmoid.fuzzify(5) === 0.5);
|
|
121
|
+
assert(sigmoid.fuzzify(5.01) > 0.99);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should handle very large slope (near constant 0.5)', function() {
|
|
125
|
+
const sigmoid = new Sigmoid(5, 10000);
|
|
126
|
+
assert(Math.abs(sigmoid.fuzzify(0) - 0.5) < 0.1);
|
|
127
|
+
assert(sigmoid.fuzzify(5) === 0.5);
|
|
128
|
+
assert(Math.abs(sigmoid.fuzzify(10) - 0.5) < 0.1);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should handle negative slope', function() {
|
|
132
|
+
const sigmoid = new Sigmoid(5, -1);
|
|
133
|
+
// Negative slope reverses the sigmoid
|
|
134
|
+
assert(sigmoid.fuzzify(3) > 0.5);
|
|
135
|
+
assert(sigmoid.fuzzify(5) === 0.5);
|
|
136
|
+
assert(sigmoid.fuzzify(7) < 0.5);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
describe('Shapes with negative x values', function() {
|
|
141
|
+
it('should handle triangle with negative x values', function() {
|
|
142
|
+
const triangle = new Triangle(-10, -5, 0);
|
|
143
|
+
assert.equal(triangle.fuzzify(-15), 0);
|
|
144
|
+
assert.equal(triangle.fuzzify(-10), 0);
|
|
145
|
+
assert.equal(triangle.fuzzify(-7.5), 0.5);
|
|
146
|
+
assert.equal(triangle.fuzzify(-5), 1);
|
|
147
|
+
assert.equal(triangle.fuzzify(-2.5), 0.5);
|
|
148
|
+
assert.equal(triangle.fuzzify(0), 0);
|
|
149
|
+
assert.equal(triangle.fuzzify(5), 0);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should handle trapezoid with negative x values', function() {
|
|
153
|
+
const trapezoid = new Trapezoid(-20, -15, -5, 0);
|
|
154
|
+
assert.equal(trapezoid.fuzzify(-25), 0);
|
|
155
|
+
assert.equal(trapezoid.fuzzify(-20), 0);
|
|
156
|
+
assert.equal(trapezoid.fuzzify(-17.5), 0.5);
|
|
157
|
+
assert.equal(trapezoid.fuzzify(-15), 1);
|
|
158
|
+
assert.equal(trapezoid.fuzzify(-10), 1);
|
|
159
|
+
assert.equal(trapezoid.fuzzify(-5), 1);
|
|
160
|
+
assert.equal(trapezoid.fuzzify(-2.5), 0.5);
|
|
161
|
+
assert.equal(trapezoid.fuzzify(0), 0);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe('Shapes with very large x values', function() {
|
|
166
|
+
it('should handle Infinity gracefully', function() {
|
|
167
|
+
const triangle = new Triangle(0, 10, 20);
|
|
168
|
+
const trapezoid = new Trapezoid(0, 10, 20, 30);
|
|
169
|
+
const grade = new Grade(0, 10);
|
|
170
|
+
const reverseGrade = new ReverseGrade(0, 10);
|
|
171
|
+
const sigmoid = new Sigmoid(10, 1);
|
|
172
|
+
|
|
173
|
+
assert.equal(triangle.fuzzify(Infinity), 0);
|
|
174
|
+
assert.equal(triangle.fuzzify(-Infinity), 0);
|
|
175
|
+
assert.equal(trapezoid.fuzzify(Infinity), 0);
|
|
176
|
+
assert.equal(trapezoid.fuzzify(-Infinity), 0);
|
|
177
|
+
assert.equal(grade.fuzzify(Infinity), 1);
|
|
178
|
+
assert.equal(grade.fuzzify(-Infinity), 0);
|
|
179
|
+
assert.equal(reverseGrade.fuzzify(Infinity), 0);
|
|
180
|
+
assert.equal(reverseGrade.fuzzify(-Infinity), 1);
|
|
181
|
+
assert.equal(sigmoid.fuzzify(Infinity), 1);
|
|
182
|
+
assert.equal(sigmoid.fuzzify(-Infinity), 0);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('should handle very large numbers', function() {
|
|
186
|
+
const triangle = new Triangle(0, 1e10, 2e10);
|
|
187
|
+
assert.equal(triangle.fuzzify(0), 0);
|
|
188
|
+
assert.equal(triangle.fuzzify(1e10), 1);
|
|
189
|
+
assert.equal(triangle.fuzzify(2e10), 0);
|
|
190
|
+
assert.equal(triangle.fuzzify(1.5e10), 0.5);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
describe('Constant edge values', function() {
|
|
195
|
+
it('should handle all possible constant values', function() {
|
|
196
|
+
assert.equal(new Constant(0).fuzzify(), 0);
|
|
197
|
+
assert.equal(new Constant(0.5).fuzzify(), 0.5);
|
|
198
|
+
assert.equal(new Constant(1).fuzzify(), 1);
|
|
199
|
+
assert.equal(new Constant(-1).fuzzify(), -1);
|
|
200
|
+
assert.equal(new Constant(2).fuzzify(), 2);
|
|
201
|
+
assert.equal(new Constant(-0.5).fuzzify(), -0.5);
|
|
202
|
+
assert.equal(new Constant(1.5).fuzzify(), 1.5);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('should ignore x parameter in fuzzify', function() {
|
|
206
|
+
const constant = new Constant(0.7);
|
|
207
|
+
assert.equal(constant.fuzzify(0), 0.7);
|
|
208
|
+
assert.equal(constant.fuzzify(100), 0.7);
|
|
209
|
+
assert.equal(constant.fuzzify(-100), 0.7);
|
|
210
|
+
assert.equal(constant.fuzzify(Infinity), 0.7);
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
describe('FuzzyFunction edge cases', function() {
|
|
215
|
+
it('should throw error for functions returning values outside [0,1]', function() {
|
|
216
|
+
const func1 = new FuzzyFunction(x => x * 2); // Returns > 1 for x > 0.5
|
|
217
|
+
assert.equal(func1.fuzzify(0.3), 0.6);
|
|
218
|
+
assert.equal(func1.fuzzify(0.5), 1); // Exactly 1 is valid
|
|
219
|
+
assert.throws(() => func1.fuzzify(0.7), /fuzzified result must be smaller than 1/);
|
|
220
|
+
|
|
221
|
+
const func2 = new FuzzyFunction(x => x - 1); // Returns < 0 for x < 1
|
|
222
|
+
assert.throws(() => func2.fuzzify(0.5), /fuzzified result must be smaller than 1/);
|
|
223
|
+
assert.equal(func2.fuzzify(1), 0); // Exactly 0 is valid
|
|
224
|
+
assert.throws(() => func2.fuzzify(0.8), /fuzzified result must be smaller than 1/);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('should handle complex mathematical functions', function() {
|
|
228
|
+
const sinFunc = new FuzzyFunction(x => (Math.sin(x) + 1) / 2);
|
|
229
|
+
assert(sinFunc.fuzzify(0) === 0.5);
|
|
230
|
+
assert(sinFunc.fuzzify(Math.PI / 2) === 1);
|
|
231
|
+
assert(sinFunc.fuzzify(3 * Math.PI / 2) === 0);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('should handle functions that throw errors', function() {
|
|
235
|
+
const errorFunc = new FuzzyFunction(x => {
|
|
236
|
+
if (x < 0) throw new Error('Negative input');
|
|
237
|
+
return x;
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
assert.equal(errorFunc.fuzzify(0.5), 0.5);
|
|
241
|
+
assert.throws(() => errorFunc.fuzzify(-1), /Negative input/);
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
});
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const { describe, it } = require('node:test');
|
|
3
|
+
const assert = require('assert');
|
|
4
|
+
const Logic = require('../lib/logic');
|
|
5
|
+
const Triangle = require('../lib/curve/triangle');
|
|
6
|
+
const Trapezoid = require('../lib/curve/trapezoid');
|
|
7
|
+
const Grade = require('../lib/curve/grade');
|
|
8
|
+
const ReverseGrade = require('../lib/curve/reverse-grade');
|
|
9
|
+
const Sigmoid = require('../lib/curve/sigmoid');
|
|
10
|
+
const Constant = require('../lib/curve/constant');
|
|
11
|
+
const FuzzyFunction = require('../lib/curve/fuzzy-function');
|
|
12
|
+
const Shape = require('../lib/curve/shape');
|
|
13
|
+
|
|
14
|
+
describe('Error Handling', function() {
|
|
15
|
+
describe('Logic class validation', function() {
|
|
16
|
+
it('should throw error when calling defuzzify without init', function() {
|
|
17
|
+
const logic = new Logic();
|
|
18
|
+
assert.throws(() => logic.defuzzify(5), /init needs to be called before performing logic/);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should throw error when calling and() without init', function() {
|
|
22
|
+
const logic = new Logic();
|
|
23
|
+
const triangle = new Triangle(0, 5, 10);
|
|
24
|
+
assert.throws(() => logic.and('hot', triangle), /init needs to be called before performing logic/);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should throw error when calling or() without init', function() {
|
|
28
|
+
const logic = new Logic();
|
|
29
|
+
const triangle = new Triangle(0, 5, 10);
|
|
30
|
+
assert.throws(() => logic.or('hot', triangle), /init needs to be called before performing logic/);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should throw error when calling not() without init', function() {
|
|
34
|
+
const logic = new Logic();
|
|
35
|
+
const triangle = new Triangle(0, 5, 10);
|
|
36
|
+
assert.throws(() => logic.not('hot', triangle), /init needs to be called before performing logic/);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should throw error for invalid output names with numbers', function() {
|
|
40
|
+
const logic = new Logic();
|
|
41
|
+
const triangle = new Triangle(0, 5, 10);
|
|
42
|
+
assert.throws(() => logic.init('hot123', triangle), /Output names can only be strings without space/);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should throw error for invalid output names with spaces', function() {
|
|
46
|
+
const logic = new Logic();
|
|
47
|
+
const triangle = new Triangle(0, 5, 10);
|
|
48
|
+
assert.throws(() => logic.init('hot cold', triangle), /Output names can only be strings without space/);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should throw error for invalid output names with special chars', function() {
|
|
52
|
+
const logic = new Logic();
|
|
53
|
+
const triangle = new Triangle(0, 5, 10);
|
|
54
|
+
assert.throws(() => logic.init('hot-cold', triangle), /Output names can only be strings without space/);
|
|
55
|
+
assert.throws(() => logic.init('hot_cold', triangle), /Output names can only be strings without space/);
|
|
56
|
+
assert.throws(() => logic.init('hot.cold', triangle), /Output names can only be strings without space/);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should allow valid output names', function() {
|
|
60
|
+
const logic = new Logic();
|
|
61
|
+
const triangle = new Triangle(0, 5, 10);
|
|
62
|
+
assert.doesNotThrow(() => logic.init('hot', triangle));
|
|
63
|
+
assert.doesNotThrow(() => logic.init('Hot', triangle));
|
|
64
|
+
assert.doesNotThrow(() => logic.init('HOT', triangle));
|
|
65
|
+
assert.doesNotThrow(() => logic.init('veryHot', triangle));
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('Shape constructor validation', function() {
|
|
70
|
+
it('should handle NaN parameters', function() {
|
|
71
|
+
const triangle = new Triangle(NaN, 5, 10);
|
|
72
|
+
// NaN in x0 means x <= NaN is false, so it falls through to other conditions
|
|
73
|
+
// The result depends on the specific implementation
|
|
74
|
+
const result = triangle.fuzzify(5);
|
|
75
|
+
assert(typeof result === 'number');
|
|
76
|
+
|
|
77
|
+
const trapezoid = new Trapezoid(0, NaN, 10, 15);
|
|
78
|
+
const result2 = trapezoid.fuzzify(5);
|
|
79
|
+
assert(typeof result2 === 'number');
|
|
80
|
+
|
|
81
|
+
const grade = new Grade(NaN, 10);
|
|
82
|
+
const result3 = grade.fuzzify(5);
|
|
83
|
+
assert(typeof result3 === 'number');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should handle Infinity parameters', function() {
|
|
87
|
+
// Triangle with Infinity produces NaN in division
|
|
88
|
+
const triangle = new Triangle(0, 10, Infinity);
|
|
89
|
+
assert.equal(triangle.fuzzify(0), 0);
|
|
90
|
+
assert.equal(triangle.fuzzify(10), 1);
|
|
91
|
+
assert(isNaN(triangle.fuzzify(20))); // (Infinity - 20) / (Infinity - 10) = NaN
|
|
92
|
+
|
|
93
|
+
const grade = new Grade(0, Infinity);
|
|
94
|
+
assert.equal(grade.fuzzify(0), 0);
|
|
95
|
+
assert.equal(grade.fuzzify(1000), 0); // 1000/Infinity = 0
|
|
96
|
+
assert.equal(grade.fuzzify(Infinity), 1); // Only at infinity
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should handle undefined parameters with defaults', function() {
|
|
100
|
+
// Most shapes don't have default parameters, they'll get undefined
|
|
101
|
+
const sigmoid = new Sigmoid(); // Has defaults
|
|
102
|
+
assert.equal(sigmoid.fuzzify(0), 0.5); // Default center=0, slope=1
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe('FuzzyFunction validation', function() {
|
|
107
|
+
it('should throw error when callback is not a function', function() {
|
|
108
|
+
assert.throws(() => {
|
|
109
|
+
const func = new FuzzyFunction('not a function');
|
|
110
|
+
func.fuzzify(5);
|
|
111
|
+
}, TypeError);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should throw error when callback is null', function() {
|
|
115
|
+
assert.throws(() => {
|
|
116
|
+
const func = new FuzzyFunction(null);
|
|
117
|
+
func.fuzzify(5);
|
|
118
|
+
}, TypeError);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should throw error when callback is undefined', function() {
|
|
122
|
+
assert.throws(() => {
|
|
123
|
+
const func = new FuzzyFunction(undefined);
|
|
124
|
+
func.fuzzify(5);
|
|
125
|
+
}, TypeError);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should throw error when callback returns non-number', function() {
|
|
129
|
+
const func = new FuzzyFunction(() => 'string');
|
|
130
|
+
// FuzzyFunction doesn't validate type, only range [0,1]
|
|
131
|
+
// String will fail the >= 0 check
|
|
132
|
+
assert.throws(() => func.fuzzify(5), /fuzzified result must be smaller than 1/);
|
|
133
|
+
|
|
134
|
+
const func2 = new FuzzyFunction(() => null);
|
|
135
|
+
// null converts to 0 in comparisons, so it passes the validation
|
|
136
|
+
assert.equal(func2.fuzzify(5), null); // Returns null as is
|
|
137
|
+
|
|
138
|
+
const func3 = new FuzzyFunction(() => undefined);
|
|
139
|
+
// undefined doesn't pass the range check
|
|
140
|
+
assert.throws(() => func3.fuzzify(5), /fuzzified result must be smaller than 1/);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should handle callback that sometimes returns valid values', function() {
|
|
144
|
+
const func = new FuzzyFunction(x => {
|
|
145
|
+
if (x < 0) return -1; // Invalid
|
|
146
|
+
if (x > 1) return 2; // Invalid
|
|
147
|
+
return x; // Valid for [0,1]
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
assert.equal(func.fuzzify(0.5), 0.5);
|
|
151
|
+
assert.throws(() => func.fuzzify(-1), /fuzzified result must be smaller than 1/);
|
|
152
|
+
assert.throws(() => func.fuzzify(2), /fuzzified result must be smaller than 1/);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
describe('ES6 class instantiation', function() {
|
|
157
|
+
it('should throw error when calling shapes without new keyword', function() {
|
|
158
|
+
assert.throws(() => Triangle(0, 5, 10), /Class constructor .* cannot be invoked without 'new'/);
|
|
159
|
+
assert.throws(() => Trapezoid(0, 5, 10, 15), /Class constructor .* cannot be invoked without 'new'/);
|
|
160
|
+
assert.throws(() => Grade(0, 10), /Class constructor .* cannot be invoked without 'new'/);
|
|
161
|
+
assert.throws(() => ReverseGrade(0, 10), /Class constructor .* cannot be invoked without 'new'/);
|
|
162
|
+
assert.throws(() => Sigmoid(5, 1), /Class constructor .* cannot be invoked without 'new'/);
|
|
163
|
+
assert.throws(() => Constant(0.5), /Class constructor .* cannot be invoked without 'new'/);
|
|
164
|
+
assert.throws(() => Shape(0, 5, 10, 15), /Class constructor .* cannot be invoked without 'new'/);
|
|
165
|
+
assert.throws(() => FuzzyFunction(x => x), /Class constructor .* cannot be invoked without 'new'/);
|
|
166
|
+
assert.throws(() => Logic(), /Class constructor .* cannot be invoked without 'new'/);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe('Empty rules array handling', function() {
|
|
171
|
+
it('should handle defuzzify with no rules', function() {
|
|
172
|
+
const logic = new Logic();
|
|
173
|
+
// Init is called but rules array is empty
|
|
174
|
+
logic.initCalled = true; // Bypass init check
|
|
175
|
+
logic.rules = []; // Empty rules
|
|
176
|
+
|
|
177
|
+
const result = logic.defuzzify(5);
|
|
178
|
+
assert.equal(result.defuzzified, 'none');
|
|
179
|
+
assert.equal(result.fuzzified, 0);
|
|
180
|
+
assert.deepEqual(result.rules, []);
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
describe('Invalid chaining sequences', function() {
|
|
185
|
+
it('should handle multiple init calls', function() {
|
|
186
|
+
const logic = new Logic();
|
|
187
|
+
const triangle1 = new Triangle(0, 5, 10);
|
|
188
|
+
const triangle2 = new Triangle(10, 15, 20);
|
|
189
|
+
|
|
190
|
+
logic.init('cold', triangle1);
|
|
191
|
+
// Second init should work (not throw)
|
|
192
|
+
assert.doesNotThrow(() => logic.init('hot', triangle2));
|
|
193
|
+
|
|
194
|
+
// But rules should contain both
|
|
195
|
+
assert.equal(logic.rules.length, 2);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('should handle chaining after defuzzify', function() {
|
|
199
|
+
const logic = new Logic();
|
|
200
|
+
const triangle = new Triangle(0, 5, 10);
|
|
201
|
+
|
|
202
|
+
logic.init('cold', triangle).and('warm', new Triangle(5, 10, 15));
|
|
203
|
+
const result1 = logic.defuzzify(7.5);
|
|
204
|
+
|
|
205
|
+
// Can continue chaining after defuzzify
|
|
206
|
+
logic.or('hot', new Triangle(10, 15, 20));
|
|
207
|
+
const result2 = logic.defuzzify(12.5);
|
|
208
|
+
|
|
209
|
+
assert.equal(logic.rules.length, 3);
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
describe('Property immutability', function() {
|
|
214
|
+
it('should verify shape properties are read-only', function() {
|
|
215
|
+
const triangle = new Triangle(0, 5, 10);
|
|
216
|
+
|
|
217
|
+
// Attempt to modify properties should throw in strict mode
|
|
218
|
+
assert.throws(() => {
|
|
219
|
+
'use strict';
|
|
220
|
+
triangle.x0 = 100;
|
|
221
|
+
}, /Cannot assign to read only property/);
|
|
222
|
+
|
|
223
|
+
// Properties should be unchanged
|
|
224
|
+
assert.equal(triangle.x0, 0);
|
|
225
|
+
assert.equal(triangle.x1, 5);
|
|
226
|
+
assert.equal(triangle.x2, 10);
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
});
|
package/test/example-test.js
CHANGED
package/test/init-called.test.js
CHANGED