Skip to content

Commit 8535be0

Browse files
ptrthomasclaude
andcommitted
feat: add WITHIN assertion support to Karate DSL
Add 'within' and '!within' operators to the match DSL syntax: - Update js.flex lexer to recognize within operators - Add operator mapping in MatchExpression.getMatchTypeName() - Update GherkinParser.isMatchOperator() to parse within - Add parser tests and integration tests in StepMatchTest 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 45afd3d commit 8535be0

5 files changed

Lines changed: 87 additions & 2 deletions

File tree

karate-core/src/test/java/io/karatelabs/core/StepMatchTest.java

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,4 +239,66 @@ void testMatchEach() {
239239
assertPassed(sr);
240240
}
241241

242+
// ========== Within ==========
243+
244+
@Test
245+
void testMatchWithinArray() {
246+
ScenarioRuntime sr = run("""
247+
* def subset = [1, 2]
248+
* def superset = [1, 2, 3, 4, 5]
249+
* match subset within superset
250+
""");
251+
assertPassed(sr);
252+
}
253+
254+
@Test
255+
void testMatchWithinArrayFailure() {
256+
ScenarioRuntime sr = run("""
257+
* def subset = [1, 6]
258+
* def superset = [1, 2, 3, 4, 5]
259+
* match subset within superset
260+
""");
261+
assertFailed(sr);
262+
}
263+
264+
@Test
265+
void testMatchWithinMap() {
266+
ScenarioRuntime sr = run("""
267+
* def subset = { name: 'bar' }
268+
* def superset = { name: 'bar', age: 30, city: 'NYC' }
269+
* match subset within superset
270+
""");
271+
assertPassed(sr);
272+
}
273+
274+
@Test
275+
void testMatchWithinMapFailure() {
276+
ScenarioRuntime sr = run("""
277+
* def subset = { name: 'baz' }
278+
* def superset = { name: 'bar', age: 30 }
279+
* match subset within superset
280+
""");
281+
assertFailed(sr);
282+
}
283+
284+
@Test
285+
void testMatchNotWithin() {
286+
ScenarioRuntime sr = run("""
287+
* def subset = [7, 8]
288+
* def superset = [1, 2, 3]
289+
* match subset !within superset
290+
""");
291+
assertPassed(sr);
292+
}
293+
294+
@Test
295+
void testMatchNotWithinFailure() {
296+
ScenarioRuntime sr = run("""
297+
* def subset = [1, 2]
298+
* def superset = [1, 2, 3]
299+
* match subset !within superset
300+
""");
301+
assertFailed(sr);
302+
}
303+
242304
}

karate-js/src/main/java/io/karatelabs/gherkin/MatchExpression.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ public String getMatchTypeName() {
7979
case "contains only deep" -> "CONTAINS_ONLY_DEEP";
8080
case "contains any" -> "CONTAINS_ANY";
8181
case "contains any deep" -> "CONTAINS_ANY_DEEP";
82+
case "within" -> "WITHIN";
83+
case "!within" -> "NOT_WITHIN";
8284
default -> throw new RuntimeException("Unknown operator: " + operator);
8385
};
8486
return each ? "EACH_" + base : base;

karate-js/src/main/java/io/karatelabs/js/GherkinParser.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -660,7 +660,8 @@ public static MatchExpression parseMatchExpression(String expression) {
660660

661661
private static boolean isMatchOperator(String text) {
662662
return text.equals("==") || text.equals("!=") ||
663-
text.startsWith("contains") || text.equals("!contains");
663+
text.startsWith("contains") || text.equals("!contains") ||
664+
text.equals("within") || text.equals("!within");
664665
}
665666

666667
}

karate-js/src/main/jflex/js.flex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ GM_PREFIX = "*"|"Given"|"When"|"Then"|"And"|"But"
7070
GM_TYPE_KEYWORD = "def"|"json"|"xml"|"xmlstring"|"yaml"|"csv"|"string"|"bytes"|"copy"
7171
GM_ASSIGN_KEYWORD = "configure"|"header"|"param"|"cookie"|"form field"|"multipart file"|"multipart field"
7272
GM_SPACED_KEYWORD = "form fields"|"multipart fields"|"multipart files"|"soap action"|"retry until"|"multipart entity"
73-
GM_MATCH_TYPE = ("=="|"!="|"contains"|"!contains") ({WS}+("only"|"any"))? ({WS}+"deep")?
73+
GM_MATCH_TYPE = ("=="|"!="|"contains"|"!contains"|"within"|"!within") ({WS}+("only"|"any"))? ({WS}+"deep")?
7474
GM_TRIPLE_QUOTE = \"\"\"
7575
GM_TAG = "@" {NOT_WSLF}+
7676

karate-js/src/test/java/io/karatelabs/gherkin/GherkinParserTest.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,4 +365,24 @@ void testParseMatchExpressionWithMethodCall() {
365365
assertEquals("read('file.txt').replaceAll(\"\\r\", \"\")", me.getExpectedExpr());
366366
}
367367

368+
@Test
369+
void testParseMatchExpressionWithin() {
370+
MatchExpression me = GherkinParser.parseMatchExpression("subset within superset");
371+
assertFalse(me.isEach());
372+
assertEquals("subset", me.getActualExpr());
373+
assertEquals("within", me.getOperator());
374+
assertEquals("superset", me.getExpectedExpr());
375+
assertEquals("WITHIN", me.getMatchTypeName());
376+
}
377+
378+
@Test
379+
void testParseMatchExpressionNotWithin() {
380+
MatchExpression me = GherkinParser.parseMatchExpression("foo !within bar");
381+
assertFalse(me.isEach());
382+
assertEquals("foo", me.getActualExpr());
383+
assertEquals("!within", me.getOperator());
384+
assertEquals("bar", me.getExpectedExpr());
385+
assertEquals("NOT_WITHIN", me.getMatchTypeName());
386+
}
387+
368388
}

0 commit comments

Comments
 (0)