diff --git a/.changeset/rotten-cobras-grin.md b/.changeset/rotten-cobras-grin.md
new file mode 100644
index 000000000..44d98356e
--- /dev/null
+++ b/.changeset/rotten-cobras-grin.md
@@ -0,0 +1,5 @@
+---
+'houdini': patch
+---
+
+Mutation list operations now work if you need to pass a `@with` directive to the fragment spread
diff --git a/e2e/kit/src/lib/utils/routes.ts b/e2e/kit/src/lib/utils/routes.ts
index 39863eda3..2d7f66bf0 100644
--- a/e2e/kit/src/lib/utils/routes.ts
+++ b/e2e/kit/src/lib/utils/routes.ts
@@ -20,6 +20,7 @@ export const routes = {
Lists_all: '/lists/all?limit=15',
Lists_fragment: '/lists/fragment',
+ Lists_mutation_insert: '/lists/mutation-insert',
blocking: '/blocking',
Stores_SSR: '/stores/ssr',
diff --git a/e2e/kit/src/routes/lists/mutation-insert/+page.gql b/e2e/kit/src/routes/lists/mutation-insert/+page.gql
new file mode 100644
index 000000000..cd6dfe2f5
--- /dev/null
+++ b/e2e/kit/src/routes/lists/mutation-insert/+page.gql
@@ -0,0 +1,11 @@
+query UsersListMutationInsertUsers($someParam: Boolean!) {
+ usersConnection(first: 5, snapshot: "users-list-mutation-insert") @list(name: "MyList") {
+ edges {
+ node {
+ id
+ name
+ testField(someParam: $someParam)
+ }
+ }
+ }
+}
diff --git a/e2e/kit/src/routes/lists/mutation-insert/+page.svelte b/e2e/kit/src/routes/lists/mutation-insert/+page.svelte
new file mode 100644
index 000000000..029159714
--- /dev/null
+++ b/e2e/kit/src/routes/lists/mutation-insert/+page.svelte
@@ -0,0 +1,38 @@
+
+
+
+
+
+ {#if $UsersListMutationInsertUsers.data}
+
+ {#each $UsersListMutationInsertUsers.data.usersConnection.edges as userEdge}
+ - {userEdge.node?.name} - {userEdge.node?.testField}
+ {/each}
+
+ {/if}
+
diff --git a/e2e/kit/src/routes/lists/mutation-insert/+page.ts b/e2e/kit/src/routes/lists/mutation-insert/+page.ts
new file mode 100644
index 000000000..138ed737b
--- /dev/null
+++ b/e2e/kit/src/routes/lists/mutation-insert/+page.ts
@@ -0,0 +1,7 @@
+import type { UsersListMutationInsertUsersVariables } from './$houdini';
+
+export const _UsersListMutationInsertUsersVariables: UsersListMutationInsertUsersVariables = () => {
+ return {
+ someParam: true
+ };
+};
diff --git a/e2e/kit/src/routes/lists/mutation-insert/spec.ts b/e2e/kit/src/routes/lists/mutation-insert/spec.ts
new file mode 100644
index 000000000..98505b460
--- /dev/null
+++ b/e2e/kit/src/routes/lists/mutation-insert/spec.ts
@@ -0,0 +1,22 @@
+import { test } from '@playwright/test';
+import { routes } from '../../../lib/utils/routes.js';
+import { expect_to_be, goto, locator_click } from '../../../lib/utils/testsHelper.js';
+
+test('mutation list insert with @with directive', async ({ page }) => {
+ await goto(page, routes.Lists_mutation_insert);
+
+ // Verify the initial page data
+ await expect_to_be(
+ page,
+ 'Bruce Willis - Hello worldSamuel Jackson - Hello worldMorgan Freeman - Hello worldTom Hanks - Hello worldWill Smith - Hello world'
+ );
+
+ // Add a user
+ await locator_click(page, `button[id="addusers"]`);
+
+ // Verify new user is at the top
+ await expect_to_be(
+ page,
+ 'Test User - Hello worldBruce Willis - Hello worldSamuel Jackson - Hello worldMorgan Freeman - Hello worldTom Hanks - Hello worldWill Smith - Hello world'
+ );
+});
diff --git a/packages/houdini/src/codegen/generators/artifacts/operations.ts b/packages/houdini/src/codegen/generators/artifacts/operations.ts
index 6d75bde49..08293c80c 100644
--- a/packages/houdini/src/codegen/generators/artifacts/operations.ts
+++ b/packages/houdini/src/codegen/generators/artifacts/operations.ts
@@ -28,8 +28,19 @@ export function operationsByPath(
// inside of the mutation could contain operations
graphql.visit(definition, {
FragmentSpread(node, _, __, ___, ancestors) {
+ // at this point, the fragment spread can contain the hashed parameters from an `@with` directive in it.
+ // if this fragment spread has a `@with` directive, strip the last `_asdf` from the name and check if that is a list fragment
+ let nameWithoutHash = node.name.value
+
+ if (
+ node.directives &&
+ node.directives.find((directive) => directive.name.value === 'with')
+ ) {
+ nameWithoutHash = nameWithoutHash.substring(0, nameWithoutHash.lastIndexOf('_'))
+ }
+
// if the fragment is not a list operation, we don't care about it now
- if (!config.isListFragment(node.name.value)) {
+ if (!config.isListFragment(nameWithoutHash)) {
return
}
@@ -44,8 +55,8 @@ export function operationsByPath(
operationObject({
config,
filepath,
- listName: config.listNameFromFragment(node.name.value),
- operationKind: config.listOperationFromFragment(node.name.value),
+ listName: config.listNameFromFragment(nameWithoutHash),
+ operationKind: config.listOperationFromFragment(nameWithoutHash),
type: parentTypeFromAncestors(config.schema, filepath, ancestors).name,
selection: node,
})
diff --git a/packages/houdini/src/codegen/generators/artifacts/tests/artifacts.test.ts b/packages/houdini/src/codegen/generators/artifacts/tests/artifacts.test.ts
index 1ba82893f..283a27345 100644
--- a/packages/houdini/src/codegen/generators/artifacts/tests/artifacts.test.ts
+++ b/packages/houdini/src/codegen/generators/artifacts/tests/artifacts.test.ts
@@ -1984,6 +1984,124 @@ describe('mutation artifacts', function () {
`)
})
+ test('insert operation and @with directive', async function () {
+ // the config to use in tests
+ const config = testConfig()
+ const docs = [
+ mockCollectedDoc(
+ `mutation A {
+ addFriend {
+ friend {
+ ...All_Users_insert @with(filter: "Hello World")
+ }
+ }
+ }`
+ ),
+ mockCollectedDoc(
+ `query TestQuery($filter: String) {
+ users(stringValue: "foo") @list(name: "All_Users") {
+ firstName
+ field(filter: $filter)
+ }
+ }`
+ ),
+ ]
+
+ // execute the generator
+ await runPipeline(config, docs)
+
+ // load the contents of the file
+ expect(docs[0]).toMatchInlineSnapshot(`
+ export default {
+ "name": "A",
+ "kind": "HoudiniMutation",
+ "hash": "b1ad7b854a43149aedac6832e6a5bff625e125f516e1fea5806bf4123c4ee687",
+
+ "raw": \`mutation A {
+ addFriend {
+ friend {
+ ...All_Users_insert_1oDy9M
+ id
+ }
+ }
+ }
+
+ fragment All_Users_insert_1oDy9M on User {
+ firstName
+ field(filter: "Hello World")
+ id
+ }
+ \`,
+
+ "rootType": "Mutation",
+
+ "selection": {
+ "fields": {
+ "addFriend": {
+ "type": "AddFriendOutput",
+ "keyRaw": "addFriend",
+
+ "selection": {
+ "fields": {
+ "friend": {
+ "type": "User",
+ "keyRaw": "friend",
+
+ "operations": [{
+ "action": "insert",
+ "list": "All_Users",
+ "position": "last"
+ }],
+
+ "selection": {
+ "fields": {
+ "firstName": {
+ "type": "String",
+ "keyRaw": "firstName"
+ },
+
+ "field": {
+ "type": "String",
+ "keyRaw": "field(filter: \\"Hello World\\")",
+ "nullable": true
+ },
+
+ "id": {
+ "type": "ID",
+ "keyRaw": "id",
+ "visible": true
+ }
+ },
+
+ "fragments": {
+ "All_Users_insert": {
+ "arguments": {
+ "filter": {
+ "kind": "StringValue",
+ "value": "Hello World"
+ }
+ }
+ }
+ }
+ },
+
+ "visible": true
+ }
+ }
+ },
+
+ "visible": true
+ }
+ }
+ },
+
+ "pluginData": {}
+ };
+
+ "HoudiniHash=ba51f7673207e362cd0ba18f1dee123fc094a90123d0657b0d56c26d021426df";
+ `)
+ })
+
test('insert operation allList', async function () {
// the config to use in tests
const config = testConfig()
@@ -2089,6 +2207,125 @@ describe('mutation artifacts', function () {
`)
})
+ test('insert operation allList and @with directive', async function () {
+ // the config to use in tests
+ const config = testConfig()
+ const docs = [
+ mockCollectedDoc(
+ `mutation A {
+ addFriend {
+ friend {
+ ...All_Users_insert @with(filter: "Hello World") @allLists
+ }
+ }
+ }`
+ ),
+ mockCollectedDoc(
+ `query TestQuery($filter: String) {
+ users(stringValue: "foo") @list(name: "All_Users") {
+ firstName
+ field(filter: $filter)
+ }
+ }`
+ ),
+ ]
+
+ // execute the generator
+ await runPipeline(config, docs)
+
+ // load the contents of the file
+ expect(docs[0]).toMatchInlineSnapshot(`
+ export default {
+ "name": "A",
+ "kind": "HoudiniMutation",
+ "hash": "b1ad7b854a43149aedac6832e6a5bff625e125f516e1fea5806bf4123c4ee687",
+
+ "raw": \`mutation A {
+ addFriend {
+ friend {
+ ...All_Users_insert_1oDy9M
+ id
+ }
+ }
+ }
+
+ fragment All_Users_insert_1oDy9M on User {
+ firstName
+ field(filter: "Hello World")
+ id
+ }
+ \`,
+
+ "rootType": "Mutation",
+
+ "selection": {
+ "fields": {
+ "addFriend": {
+ "type": "AddFriendOutput",
+ "keyRaw": "addFriend",
+
+ "selection": {
+ "fields": {
+ "friend": {
+ "type": "User",
+ "keyRaw": "friend",
+
+ "operations": [{
+ "action": "insert",
+ "list": "All_Users",
+ "position": "last",
+ "target": "all"
+ }],
+
+ "selection": {
+ "fields": {
+ "firstName": {
+ "type": "String",
+ "keyRaw": "firstName"
+ },
+
+ "field": {
+ "type": "String",
+ "keyRaw": "field(filter: \\"Hello World\\")",
+ "nullable": true
+ },
+
+ "id": {
+ "type": "ID",
+ "keyRaw": "id",
+ "visible": true
+ }
+ },
+
+ "fragments": {
+ "All_Users_insert": {
+ "arguments": {
+ "filter": {
+ "kind": "StringValue",
+ "value": "Hello World"
+ }
+ }
+ }
+ }
+ },
+
+ "visible": true
+ }
+ }
+ },
+
+ "visible": true
+ }
+ }
+ },
+
+ "pluginData": {}
+ };
+
+ "HoudiniHash=f6b965893f6a89f0d97c9a63645b36de599756e3e135d8912b1e0b741164caeb";
+ `)
+ })
+
test('remove operation allList', async function () {
// the config to use in tests
const config = testConfig()
@@ -2292,6 +2529,125 @@ describe('mutation artifacts', function () {
`)
})
+ test('toggle operation allList and @with directive', async function () {
+ // the config to use in tests
+ const config = testConfig()
+ const docs = [
+ mockCollectedDoc(
+ `mutation A {
+ addFriend {
+ friend {
+ ...All_Users_toggle @with(filter: "Hello World") @allLists @prepend
+ }
+ }
+ }`
+ ),
+ mockCollectedDoc(
+ `query TestQuery($filter: String) {
+ users(stringValue: "foo") @list(name: "All_Users") {
+ firstName
+ field(filter: $filter)
+ }
+ }`
+ ),
+ ]
+
+ // execute the generator
+ await runPipeline(config, docs)
+
+ // load the contents of the file
+ expect(docs[0]).toMatchInlineSnapshot(`
+ export default {
+ "name": "A",
+ "kind": "HoudiniMutation",
+ "hash": "d7187de06687137a262178ad23eecf315461cd5cef17e2b384cbcdd25fe1e752",
+
+ "raw": \`mutation A {
+ addFriend {
+ friend {
+ ...All_Users_toggle_1oDy9M
+ id
+ }
+ }
+ }
+
+ fragment All_Users_toggle_1oDy9M on User {
+ firstName
+ field(filter: "Hello World")
+ id
+ }
+ \`,
+
+ "rootType": "Mutation",
+
+ "selection": {
+ "fields": {
+ "addFriend": {
+ "type": "AddFriendOutput",
+ "keyRaw": "addFriend",
+
+ "selection": {
+ "fields": {
+ "friend": {
+ "type": "User",
+ "keyRaw": "friend",
+
+ "operations": [{
+ "action": "toggle",
+ "list": "All_Users",
+ "position": "first",
+ "target": "all"
+ }],
+
+ "selection": {
+ "fields": {
+ "firstName": {
+ "type": "String",
+ "keyRaw": "firstName"
+ },
+
+ "field": {
+ "type": "String",
+ "keyRaw": "field(filter: \\"Hello World\\")",
+ "nullable": true
+ },
+
+ "id": {
+ "type": "ID",
+ "keyRaw": "id",
+ "visible": true
+ }
+ },
+
+ "fragments": {
+ "All_Users_toggle": {
+ "arguments": {
+ "filter": {
+ "kind": "StringValue",
+ "value": "Hello World"
+ }
+ }
+ }
+ }
+ },
+
+ "visible": true
+ }
+ }
+ },
+
+ "visible": true
+ }
+ }
+ },
+
+ "pluginData": {}
+ };
+
+ "HoudiniHash=2b2f4aafb54ec3bdba80389398aff1d4b6f478a5e58ec714bb6aa82c48e987b5";
+ `)
+ })
+
test('insert operation allList by default in config', async function () {
// the config to use in tests
const docs = [
@@ -2612,6 +2968,124 @@ describe('mutation artifacts', function () {
`)
})
+ test('toggle operation and @with directive', async function () {
+ // the config to use in tests
+ const config = testConfig()
+ const docs = [
+ mockCollectedDoc(
+ `mutation A {
+ addFriend {
+ friend {
+ ...All_Users_toggle @with(filter: "Hello World")
+ }
+ }
+ }`
+ ),
+ mockCollectedDoc(
+ `query TestQuery($filter: String) {
+ users(stringValue: "foo") @list(name: "All_Users") {
+ firstName
+ field(filter: $filter)
+ }
+ }`
+ ),
+ ]
+
+ // execute the generator
+ await runPipeline(config, docs)
+
+ // load the contents of the file
+ expect(docs[0]).toMatchInlineSnapshot(`
+ export default {
+ "name": "A",
+ "kind": "HoudiniMutation",
+ "hash": "d7187de06687137a262178ad23eecf315461cd5cef17e2b384cbcdd25fe1e752",
+
+ "raw": \`mutation A {
+ addFriend {
+ friend {
+ ...All_Users_toggle_1oDy9M
+ id
+ }
+ }
+ }
+
+ fragment All_Users_toggle_1oDy9M on User {
+ firstName
+ field(filter: "Hello World")
+ id
+ }
+ \`,
+
+ "rootType": "Mutation",
+
+ "selection": {
+ "fields": {
+ "addFriend": {
+ "type": "AddFriendOutput",
+ "keyRaw": "addFriend",
+
+ "selection": {
+ "fields": {
+ "friend": {
+ "type": "User",
+ "keyRaw": "friend",
+
+ "operations": [{
+ "action": "toggle",
+ "list": "All_Users",
+ "position": "last"
+ }],
+
+ "selection": {
+ "fields": {
+ "firstName": {
+ "type": "String",
+ "keyRaw": "firstName"
+ },
+
+ "field": {
+ "type": "String",
+ "keyRaw": "field(filter: \\"Hello World\\")",
+ "nullable": true
+ },
+
+ "id": {
+ "type": "ID",
+ "keyRaw": "id",
+ "visible": true
+ }
+ },
+
+ "fragments": {
+ "All_Users_toggle": {
+ "arguments": {
+ "filter": {
+ "kind": "StringValue",
+ "value": "Hello World"
+ }
+ }
+ }
+ }
+ },
+
+ "visible": true
+ }
+ }
+ },
+
+ "visible": true
+ }
+ }
+ },
+
+ "pluginData": {}
+ };
+
+ "HoudiniHash=4a95f1e6dc9fdce153311e84965a99e72f76fc56a063fced1e28efefc50f143a";
+ `)
+ })
+
test('remove operation', async function () {
// the config to use in tests
const config = testConfig()