diff --git a/app/components/form/fields/SshKeysField.tsx b/app/components/form/fields/SshKeysField.tsx index e03d4d2c7..390ee4c39 100644 --- a/app/components/form/fields/SshKeysField.tsx +++ b/app/components/form/fields/SshKeysField.tsx @@ -20,6 +20,7 @@ import { EmptyMessage } from '~/ui/lib/EmptyMessage' import { FieldLabel } from '~/ui/lib/FieldLabel' import { Message } from '~/ui/lib/Message' import { TextInputHint } from '~/ui/lib/TextInput' +import { isSubset } from '~/util/array' import { CheckboxField } from './CheckboxField' import { ErrorMessage } from './ErrorMessage' @@ -73,7 +74,16 @@ export function SshKeysField({ }, }) - const allAreSelected = allKeys.length === selectedKeys.length + // do this with a subset instead of just counting the items because there's a + // weird window right after adding but before the invalidate has gone through + // where you can have the new one selected but it's not in the list of all + // keys, which can cause the counts to match even though the sets don't + const allAreSelected = + allKeys.length > 0 && + isSubset( + allKeys.map((k) => k.id), + new Set(selectedKeys) + ) return (
diff --git a/app/util/array.spec.tsx b/app/util/array.spec.tsx index 34dfcb6c0..3e0dd99ed 100644 --- a/app/util/array.spec.tsx +++ b/app/util/array.spec.tsx @@ -8,7 +8,14 @@ import type { JSX, ReactElement } from 'react' import { expect, test } from 'vitest' -import { groupBy, intersperse, isSetEqual, setDiff, setIntersection } from './array' +import { + groupBy, + intersperse, + isSetEqual, + isSubset, + setDiff, + setIntersection, +} from './array' test('groupBy', () => { expect( @@ -73,6 +80,17 @@ test('isSetEqual', () => { expect(isSetEqual(new Set([{}]), new Set([{}]))).toBe(false) }) +test('isSubset', () => { + expect(isSubset(new Set(), new Set())).toBe(true) + expect(isSubset(new Set(), new Set(['a']))).toBe(true) + expect(isSubset(new Set(['a']), new Set(['a', 'b']))).toBe(true) + expect(isSubset(new Set(['a', 'b']), new Set(['a', 'b']))).toBe(true) + + expect(isSubset(new Set(['a']), new Set())).toBe(false) + expect(isSubset(new Set(['a', 'b']), new Set(['a']))).toBe(false) + expect(isSubset(new Set(['c']), new Set(['a', 'b']))).toBe(false) +}) + test('setDiff', () => { expect(setDiff(new Set(), new Set())).toEqual(new Set()) expect(setDiff(new Set(['a']), new Set())).toEqual(new Set(['a'])) diff --git a/app/util/array.ts b/app/util/array.ts index 004ef00e2..c07c48849 100644 --- a/app/util/array.ts +++ b/app/util/array.ts @@ -49,6 +49,14 @@ export function isSetEqual(a: Set, b: Set): boolean { return true } +/** Is `a ⊆ b`? */ +export function isSubset(a: Iterable, b: ReadonlySet): boolean { + for (const item of a) { + if (!b.has(item)) return false + } + return true +} + /** Set `a - b` */ export function setDiff(a: ReadonlySet, b: ReadonlySet): Set { return new Set([...a].filter((x) => !b.has(x)))