Skip to content

Commit 5aafefd

Browse files
authored
Merge pull request #6170 from continuedev/tomasz/fix-always-apply
test: alwaysApply
2 parents 39bdcaf + 06c2206 commit 5aafefd

File tree

1 file changed

+186
-0
lines changed

1 file changed

+186
-0
lines changed
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
import { describe, expect, it } from "vitest";
2+
import {
3+
ContextItemId,
4+
ContextItemWithId,
5+
RuleWithSource,
6+
UserChatMessage,
7+
} from "../..";
8+
import { getApplicableRules } from "./getSystemMessageWithRules";
9+
10+
describe("alwaysApply Behavior", () => {
11+
// Common test fixtures
12+
const testFile = "src/app.ts";
13+
const matchingFile = "src/components/Button.tsx";
14+
const nonMatchingFile = "src/utils/helper.js"; // Not matching *.tsx glob
15+
16+
// File context items
17+
const matchingFileContext: ContextItemWithId = {
18+
id: { providerTitle: "file", itemId: "match1" } as ContextItemId,
19+
uri: { type: "file", value: matchingFile },
20+
content: "export const Button = () => {...}",
21+
name: "Button.tsx",
22+
description: "Component file",
23+
};
24+
25+
const nonMatchingFileContext: ContextItemWithId = {
26+
id: { providerTitle: "file", itemId: "nonmatch1" } as ContextItemId,
27+
uri: { type: "file", value: nonMatchingFile },
28+
content: "export const helper = () => {...}",
29+
name: "helper.js",
30+
description: "Helper file",
31+
};
32+
33+
// Message with no file references
34+
const messageWithoutFile: UserChatMessage = {
35+
role: "user",
36+
content: "Can you help me understand how this works?",
37+
};
38+
39+
it("alwaysApply: true - Always include the rule, regardless of file context", () => {
40+
// Rule with alwaysApply: true
41+
const alwaysApplyRule: RuleWithSource = {
42+
name: "Always Apply Rule",
43+
rule: "This rule should always be applied",
44+
alwaysApply: true,
45+
globs: "**/*.tsx", // Should be ignored since alwaysApply is true
46+
source: "rules-block",
47+
ruleFile: ".continue/rules.md",
48+
};
49+
50+
// Test with no file context
51+
let applicableRules = getApplicableRules(
52+
messageWithoutFile,
53+
[alwaysApplyRule],
54+
[],
55+
);
56+
expect(applicableRules).toHaveLength(1);
57+
expect(applicableRules[0].name).toBe("Always Apply Rule");
58+
59+
// Test with matching file
60+
applicableRules = getApplicableRules(
61+
undefined,
62+
[alwaysApplyRule],
63+
[matchingFileContext],
64+
);
65+
expect(applicableRules).toHaveLength(1);
66+
expect(applicableRules[0].name).toBe("Always Apply Rule");
67+
68+
// Test with non-matching file
69+
applicableRules = getApplicableRules(
70+
undefined,
71+
[alwaysApplyRule],
72+
[nonMatchingFileContext],
73+
);
74+
expect(applicableRules).toHaveLength(1);
75+
expect(applicableRules[0].name).toBe("Always Apply Rule");
76+
});
77+
78+
it("alwaysApply: false - Only include if globs exist AND match file context", () => {
79+
// Rule with alwaysApply: false and globs
80+
const conditionalRule: RuleWithSource = {
81+
name: "Conditional Rule",
82+
rule: "Apply only to matching files",
83+
alwaysApply: false,
84+
globs: "**/*.tsx",
85+
source: "rules-block",
86+
ruleFile: ".continue/rules.md",
87+
};
88+
89+
// Rule with alwaysApply: false and no globs
90+
const neverApplyRule: RuleWithSource = {
91+
name: "Never Apply Rule",
92+
rule: "This rule should never apply",
93+
alwaysApply: false,
94+
// No globs
95+
source: "rules-block",
96+
ruleFile: ".continue/rules.md",
97+
};
98+
99+
// Test with no file context
100+
let applicableRules = getApplicableRules(
101+
messageWithoutFile,
102+
[conditionalRule, neverApplyRule],
103+
[],
104+
);
105+
expect(applicableRules).toHaveLength(0); // No rules should apply
106+
107+
// Test with matching file
108+
applicableRules = getApplicableRules(
109+
undefined,
110+
[conditionalRule, neverApplyRule],
111+
[matchingFileContext],
112+
);
113+
expect(applicableRules).toHaveLength(1);
114+
expect(applicableRules[0].name).toBe("Conditional Rule");
115+
expect(applicableRules.map((r) => r.name)).not.toContain(
116+
"Never Apply Rule",
117+
);
118+
119+
// Test with non-matching file
120+
applicableRules = getApplicableRules(
121+
undefined,
122+
[conditionalRule, neverApplyRule],
123+
[nonMatchingFileContext],
124+
);
125+
expect(applicableRules).toHaveLength(0); // No rules should apply
126+
});
127+
128+
it("alwaysApply: undefined - Default behavior: include if no globs exist OR globs exist and match", () => {
129+
// Rule with undefined alwaysApply and no globs (should behave like a global rule)
130+
const defaultNoGlobsRule: RuleWithSource = {
131+
name: "Default No Globs Rule",
132+
rule: "Default rule with no globs",
133+
source: "rules-block",
134+
ruleFile: ".continue/rules.md",
135+
// No alwaysApply, no globs
136+
};
137+
138+
// Rule with undefined alwaysApply and globs
139+
const defaultWithGlobsRule: RuleWithSource = {
140+
name: "Default With Globs Rule",
141+
rule: "Default rule with globs",
142+
globs: "**/*.tsx",
143+
source: "rules-block",
144+
ruleFile: ".continue/rules.md",
145+
// No alwaysApply, with globs
146+
};
147+
148+
// Test with no file context
149+
let applicableRules = getApplicableRules(
150+
messageWithoutFile,
151+
[defaultNoGlobsRule, defaultWithGlobsRule],
152+
[],
153+
);
154+
expect(applicableRules).toHaveLength(1);
155+
expect(applicableRules[0].name).toBe("Default No Globs Rule");
156+
expect(applicableRules.map((r) => r.name)).not.toContain(
157+
"Default With Globs Rule",
158+
);
159+
160+
// Test with matching file
161+
applicableRules = getApplicableRules(
162+
undefined,
163+
[defaultNoGlobsRule, defaultWithGlobsRule],
164+
[matchingFileContext],
165+
);
166+
expect(applicableRules).toHaveLength(2);
167+
expect(applicableRules.map((r) => r.name)).toContain(
168+
"Default No Globs Rule",
169+
);
170+
expect(applicableRules.map((r) => r.name)).toContain(
171+
"Default With Globs Rule",
172+
);
173+
174+
// Test with non-matching file
175+
applicableRules = getApplicableRules(
176+
undefined,
177+
[defaultNoGlobsRule, defaultWithGlobsRule],
178+
[nonMatchingFileContext],
179+
);
180+
expect(applicableRules).toHaveLength(1);
181+
expect(applicableRules[0].name).toBe("Default No Globs Rule");
182+
expect(applicableRules.map((r) => r.name)).not.toContain(
183+
"Default With Globs Rule",
184+
);
185+
});
186+
});

0 commit comments

Comments
 (0)