Skip to content

Commit 5182600

Browse files
Merge branch 'main' into fix/double-pipe-issue
2 parents 8731d97 + 7e3da6f commit 5182600

File tree

2 files changed

+315
-0
lines changed

2 files changed

+315
-0
lines changed

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,31 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project
66
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## v1.1.3 - 2025-01-27
9+
10+
### Fixed
11+
12+
- **Logical operators without spaces**: Fixed issue where logical operators (`||`, `&&`, `|`, `&`)
13+
were not working when used without spaces around them (e.g.,
14+
`properties.view-all||properties.view-own`)
15+
- **Permission names with hyphens**: Updated regex pattern to properly support permission names
16+
containing hyphens (e.g., `properties.view-all`, `user-profile.edit`)
17+
- **Operator normalization**: Improved operator normalization logic to prevent double replacement
18+
issues
19+
20+
### Added
21+
22+
- **Comprehensive test coverage**: Added extensive test suite for logical operators without spaces,
23+
covering various edge cases and scenarios
24+
- **Hyphenated permission support**: Full support for permission names with hyphens in logical
25+
expressions
26+
27+
### Technical Details
28+
29+
- Updated regex pattern from `[a-zA-Z0-9_.*?]*` to `[a-zA-Z0-9_.*?-]*` to include hyphens
30+
- Fixed operator normalization using negative lookbehind to prevent double replacement
31+
- Added 12 new test cases covering no-spaces scenarios, hyphenated permissions, and edge cases
32+
833
## v1.1.2 - 2025-09-19
934

1035
### What's Changed
Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
import { renderHook } from '@testing-library/react';
2+
import { usePermissions } from '../hooks/use-permissions';
3+
4+
// Mock @inertiajs/react
5+
jest.mock('@inertiajs/react', () => ({
6+
usePage: jest.fn(),
7+
}));
8+
9+
import { usePage } from '@inertiajs/react';
10+
const mockUsePage = usePage as jest.MockedFunction<typeof usePage>;
11+
12+
describe('Logical Operators Without Spaces', () => {
13+
beforeEach(() => {
14+
jest.clearAllMocks();
15+
});
16+
17+
const mockPageProps = (userPermissions: string[] = []) => {
18+
mockUsePage.mockReturnValue({
19+
props: {
20+
auth: {
21+
user: {
22+
permissions: userPermissions,
23+
},
24+
},
25+
},
26+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
27+
} as any);
28+
};
29+
30+
describe('OR operators without spaces', () => {
31+
it('should handle || without spaces around operators', () => {
32+
mockPageProps(['properties.view-all', 'properties.view-own']);
33+
34+
const { result } = renderHook(() => usePermissions());
35+
36+
// Test various OR expressions without spaces
37+
expect(
38+
result.current.hasPermission('properties.view-all||properties.view-own')
39+
).toBe(true);
40+
expect(result.current.hasPermission('users.create||posts.view')).toBe(
41+
false
42+
); // Neither permission exists
43+
expect(
44+
result.current.hasPermission('properties.view-all||users.create')
45+
).toBe(true); // One permission exists
46+
});
47+
48+
it('should handle single | without spaces around operators', () => {
49+
mockPageProps(['properties.view-all', 'properties.view-own']);
50+
51+
const { result } = renderHook(() => usePermissions());
52+
53+
// Test single | expressions without spaces
54+
expect(
55+
result.current.hasPermission('properties.view-all|properties.view-own')
56+
).toBe(true);
57+
expect(
58+
result.current.hasPermission('properties.view-all|users.create')
59+
).toBe(true); // One permission exists
60+
expect(result.current.hasPermission('users.create|posts.view')).toBe(
61+
false
62+
); // Neither permission exists
63+
});
64+
});
65+
66+
describe('AND operators without spaces', () => {
67+
it('should handle && without spaces around operators', () => {
68+
mockPageProps(['properties.view-all', 'properties.view-own']);
69+
70+
const { result } = renderHook(() => usePermissions());
71+
72+
// Test various AND expressions without spaces
73+
expect(
74+
result.current.hasPermission('properties.view-all&&properties.view-own')
75+
).toBe(true);
76+
expect(
77+
result.current.hasPermission('properties.view-all&&users.create')
78+
).toBe(false); // One permission missing
79+
expect(result.current.hasPermission('users.create&&posts.view')).toBe(
80+
false
81+
); // Both permissions missing
82+
});
83+
84+
it('should handle single & without spaces around operators', () => {
85+
mockPageProps(['properties.view-all', 'properties.view-own']);
86+
87+
const { result } = renderHook(() => usePermissions());
88+
89+
// Test single & expressions without spaces
90+
expect(
91+
result.current.hasPermission('properties.view-all&properties.view-own')
92+
).toBe(true);
93+
expect(
94+
result.current.hasPermission('properties.view-all&users.create')
95+
).toBe(false); // One permission missing
96+
expect(result.current.hasPermission('users.create&posts.view')).toBe(
97+
false
98+
); // Both permissions missing
99+
});
100+
});
101+
102+
describe('Complex expressions without spaces', () => {
103+
it('should handle parentheses with no spaces around operators', () => {
104+
mockPageProps(['properties.view-all', 'admin.access']);
105+
106+
const { result } = renderHook(() => usePermissions());
107+
108+
// Test complex expressions without spaces
109+
expect(
110+
result.current.hasPermission(
111+
'(properties.view-all||users.create)&&admin.access'
112+
)
113+
).toBe(true);
114+
expect(
115+
result.current.hasPermission(
116+
'(properties.view-all||users.create)&&admin.delete'
117+
)
118+
).toBe(false);
119+
expect(
120+
result.current.hasPermission('(users.create||posts.view)&&admin.access')
121+
).toBe(false);
122+
});
123+
124+
it('should handle mixed operators without spaces', () => {
125+
mockPageProps(['properties.view-all', 'admin.access', 'reports.read']);
126+
127+
const { result } = renderHook(() => usePermissions());
128+
129+
// Test mixed operators without spaces
130+
expect(
131+
result.current.hasPermission(
132+
'properties.view-all&&admin.access||reports.read'
133+
)
134+
).toBe(true);
135+
expect(
136+
result.current.hasPermission(
137+
'properties.view-all&admin.access|reports.read'
138+
)
139+
).toBe(true);
140+
expect(
141+
result.current.hasPermission('users.create&&admin.access||reports.read')
142+
).toBe(true); // reports.read exists
143+
});
144+
});
145+
146+
describe('Permission names with hyphens', () => {
147+
it('should handle permission names with hyphens in expressions without spaces', () => {
148+
mockPageProps([
149+
'user-profile.edit',
150+
'api-access.read',
151+
'system-config.update',
152+
]);
153+
154+
const { result } = renderHook(() => usePermissions());
155+
156+
// Test hyphenated permission names without spaces
157+
expect(
158+
result.current.hasPermission('user-profile.edit||api-access.read')
159+
).toBe(true);
160+
expect(
161+
result.current.hasPermission('user-profile.edit&&api-access.read')
162+
).toBe(true);
163+
expect(
164+
result.current.hasPermission('user-profile.edit&&system-config.update')
165+
).toBe(true);
166+
expect(
167+
result.current.hasPermission('user-profile.edit&&users.create')
168+
).toBe(false); // users.create doesn't exist
169+
});
170+
171+
it('should handle complex hyphenated permission expressions', () => {
172+
mockPageProps([
173+
'user-profile.edit',
174+
'api-access.read',
175+
'admin-panel.access',
176+
]);
177+
178+
const { result } = renderHook(() => usePermissions());
179+
180+
// Test complex expressions with hyphenated permissions
181+
expect(
182+
result.current.hasPermission(
183+
'(user-profile.edit||api-access.read)&&admin-panel.access'
184+
)
185+
).toBe(true);
186+
expect(
187+
result.current.hasPermission(
188+
'user-profile.edit&&(api-access.read||system-config.update)'
189+
)
190+
).toBe(true);
191+
expect(
192+
result.current.hasPermission(
193+
'(user-profile.edit||api-access.read)&&system-config.update'
194+
)
195+
).toBe(false);
196+
});
197+
});
198+
199+
describe('Boolean literals without spaces', () => {
200+
it('should handle boolean literals in expressions without spaces', () => {
201+
mockPageProps(['properties.view-all']);
202+
203+
const { result } = renderHook(() => usePermissions());
204+
205+
// Test boolean literals without spaces
206+
expect(result.current.hasPermission('true||properties.view-all')).toBe(
207+
true
208+
);
209+
expect(result.current.hasPermission('false||properties.view-all')).toBe(
210+
true
211+
);
212+
expect(result.current.hasPermission('true&&properties.view-all')).toBe(
213+
true
214+
);
215+
expect(result.current.hasPermission('false&&properties.view-all')).toBe(
216+
false
217+
);
218+
expect(result.current.hasPermission('properties.view-all||true')).toBe(
219+
true
220+
);
221+
expect(result.current.hasPermission('properties.view-all&&false')).toBe(
222+
false
223+
);
224+
});
225+
});
226+
227+
describe('Edge cases without spaces', () => {
228+
it('should handle expressions with multiple consecutive operators', () => {
229+
mockPageProps(['properties.view-all', 'properties.view-own']);
230+
231+
const { result } = renderHook(() => usePermissions());
232+
233+
// These should be handled gracefully (though they're not valid expressions)
234+
expect(
235+
result.current.hasPermission(
236+
'properties.view-all||||properties.view-own'
237+
)
238+
).toBe(false); // Invalid
239+
expect(
240+
result.current.hasPermission(
241+
'properties.view-all&&&&properties.view-own'
242+
)
243+
).toBe(false); // Invalid
244+
});
245+
246+
it('should handle expressions starting or ending with operators', () => {
247+
mockPageProps(['properties.view-all']);
248+
249+
const { result } = renderHook(() => usePermissions());
250+
251+
// These should be handled gracefully
252+
expect(result.current.hasPermission('||properties.view-all')).toBe(false); // Invalid
253+
expect(result.current.hasPermission('properties.view-all||')).toBe(false); // Invalid
254+
expect(result.current.hasPermission('&&properties.view-all')).toBe(false); // Invalid
255+
expect(result.current.hasPermission('properties.view-all&&')).toBe(false); // Invalid
256+
});
257+
});
258+
259+
describe('Comparison with spaced expressions', () => {
260+
it('should produce the same results with and without spaces', () => {
261+
mockPageProps([
262+
'properties.view-all',
263+
'properties.view-own',
264+
'admin.access',
265+
]);
266+
267+
const { result } = renderHook(() => usePermissions());
268+
269+
// Test that expressions with and without spaces produce the same results
270+
const expressions = [
271+
'properties.view-all||properties.view-own',
272+
'properties.view-all || properties.view-own',
273+
'properties.view-all&&admin.access',
274+
'properties.view-all && admin.access',
275+
'properties.view-all|properties.view-own',
276+
'properties.view-all | properties.view-own',
277+
'properties.view-all&admin.access',
278+
'properties.view-all & admin.access',
279+
'(properties.view-all||properties.view-own)&&admin.access',
280+
'(properties.view-all || properties.view-own) && admin.access',
281+
];
282+
283+
expressions.forEach(expr => {
284+
const hasPermission = result.current.hasPermission(expr);
285+
expect(hasPermission).toBeDefined();
286+
expect(typeof hasPermission).toBe('boolean');
287+
});
288+
});
289+
});
290+
});

0 commit comments

Comments
 (0)