👁️
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

handle set mapping for syntax search

+82 -3
+42
src/lib/search/__tests__/integration.test.ts
··· 699 699 }); 700 700 }); 701 701 702 + describe("set: arena code normalization", () => { 703 + const domCard = { set: "dom" } as Card; 704 + const dd1Card = { set: "dd1" } as Card; 705 + const evgCard = { set: "evg" } as Card; 706 + 707 + it("set:dar finds Dominaria (dom) cards", () => { 708 + // "dar" is Arena's code for Dominaria 709 + const result = search("set:dar"); 710 + expect(result.ok).toBe(true); 711 + if (result.ok) { 712 + expect(result.value.match(domCard)).toBe(true); 713 + } 714 + }); 715 + 716 + it("set:dom still works directly", () => { 717 + const result = search("set:dom"); 718 + expect(result.ok).toBe(true); 719 + if (result.ok) { 720 + expect(result.value.match(domCard)).toBe(true); 721 + } 722 + }); 723 + 724 + it("set:evg finds Anthology (evg), not dd1", () => { 725 + // "evg" is shadowed - Arena uses it for dd1, but Scryfall has its own evg set 726 + // We should NOT map it to dd1 in search to avoid hiding paper set 727 + const result = search("set:evg"); 728 + expect(result.ok).toBe(true); 729 + if (result.ok) { 730 + expect(result.value.match(evgCard)).toBe(true); 731 + expect(result.value.match(dd1Card)).toBe(false); 732 + } 733 + }); 734 + 735 + it("in:dar also normalizes arena codes", () => { 736 + const result = search("in:dar"); 737 + expect(result.ok).toBe(true); 738 + if (result.ok) { 739 + expect(result.value.match(domCard)).toBe(true); 740 + } 741 + }); 742 + }); 743 + 702 744 describe("complex queries", () => { 703 745 it("commander deckbuilding query", async () => { 704 746 const elves = await cards.get("Llanowar Elves");
+40 -3
src/lib/search/fields.ts
··· 7 7 canBePauperCommander, 8 8 hasPartnerMechanic, 9 9 } from "@/lib/deck-validation/card-utils"; 10 + import { normalizeSetCodeForSearch } from "@/lib/set-symbols"; 10 11 import type { Card } from "../scryfall-types"; 11 12 import { compareColors } from "./colors"; 12 13 import type { ··· 134 135 135 136 // Set/printing (discrete fields use exact match for ':') 136 137 case "set": 137 - return ok(compileTextField((c) => c.set, operator, value, true)); 138 + return ok(compileSetField(operator, value)); 138 139 139 140 case "settype": 140 141 return ok(compileTextField((c) => c.set_type, operator, value, true)); ··· 281 282 } 282 283 283 284 /** 285 + * Compile set field matcher with Arena/MTGO code normalization 286 + * 287 + * Maps arena codes (dar→dom) to scryfall codes, but only for unambiguous mappings. 288 + * Shadowed codes (evg, med) are left as-is to avoid hiding real paper sets. 289 + */ 290 + function compileSetField( 291 + operator: ComparisonOp, 292 + value: FieldValue, 293 + ): CardPredicate { 294 + if (value.kind === "regex") { 295 + const pattern = value.pattern; 296 + return (card) => (card.set ? pattern.test(card.set) : false); 297 + } 298 + 299 + if (value.kind !== "string") { 300 + return () => false; 301 + } 302 + 303 + // normalizeSetCodeForSearch returns lowercase, but card.set may not be 304 + const searchValue = normalizeSetCodeForSearch(value.value); 305 + 306 + switch (operator) { 307 + case ":": 308 + case "=": 309 + return (card) => card.set?.toLowerCase() === searchValue; 310 + case "!=": 311 + return (card) => 312 + card.set ? card.set.toLowerCase() !== searchValue : true; 313 + default: 314 + return (card) => card.set?.toLowerCase() === searchValue; 315 + } 316 + } 317 + 318 + /** 284 319 * Compile color field matcher 285 320 */ 286 321 function compileColorField( ··· 583 618 // Fall back to set code or language 584 619 // Set codes are typically 3-4 chars, languages are 2-3 chars 585 620 // Check both - if either matches, include the card 621 + // Normalize set code for arena/mtgo compatibility (dar→dom, etc.) 622 + const normalizedSetCode = normalizeSetCodeForSearch(searchValue); 586 623 return isNegated 587 624 ? (card) => 588 - card.set?.toLowerCase() !== searchValue && 625 + card.set?.toLowerCase() !== normalizedSetCode && 589 626 card.lang?.toLowerCase() !== searchValue 590 627 : (card) => 591 - card.set?.toLowerCase() === searchValue || 628 + card.set?.toLowerCase() === normalizedSetCode || 592 629 card.lang?.toLowerCase() === searchValue; 593 630 } 594 631