Skip to content

Commit

Permalink
fix: Setting and checking Custom Status ('Single Select') fields yiel…
Browse files Browse the repository at this point in the history
…ds errors.
  • Loading branch information
adam-coster committed Aug 26, 2021
1 parent 223ad66 commit d34b2b6
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 25 deletions.
4 changes: 2 additions & 2 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
- ✅ Checkbox
- ✅ Date
- ✅ Link
- 😒 Status
- ✅ Status
- Multiple select
- Members
- Tags
- Multiple select
- Timeline
- ~~Time~~ (only used in Enterprise)
- Are custom fields unsettable? Maybe by sending a `null`?
Expand Down
45 changes: 26 additions & 19 deletions src/lib/entities/BravoCustomField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ import type { DataFavroCustomFieldDefinition } from '$/types/FavroCustomFieldTyp
import { BravoEntity } from '$lib/BravoEntity.js';
import { assertBravoClaim, BravoError } from '../errors.js';

type DataFavroCustomFieldTypeWithChoices =
| 'Multiple select'
| 'Single select'
| 'Tags';

type CustomFieldOption =
DataFavroCustomFieldDefinition<DataFavroCustomFieldTypeWithChoices>['customFieldItems'];

type BravoHumanFriendlyFieldValues = {
Number: number;
Time: number;
Expand All @@ -25,16 +33,11 @@ type BravoHumanFriendlyFieldValues = {
};
Link: { url: string; text: string };
Members: string[];
Tags: string[];
'Single select': string;
'Multiple select': string;
Tags: CustomFieldOption[];
'Single select': CustomFieldOption;
'Multiple select': CustomFieldOption[];
};

type DataFavroCustomFieldTypeWithChoices =
| 'Multiple select'
| 'Single select'
| 'Tags';

export class BravoCustomFieldDefinition<
TypeName extends DataFavroCustomFieldType,
> extends BravoEntity<DataFavroCustomFieldDefinition<TypeName>> {
Expand Down Expand Up @@ -140,16 +143,18 @@ export class BravoCustomField<TypeName extends DataFavroCustomFieldType> {
* Whether or not there was a defined value provided
* upon instantiation.*/
get isSet() {
return this.value !== undefined;
return Array.isArray(this.value)
? this.value.length > 0
: this.value !== undefined;
}

/**
* If this type of field has options, and is also set,
* the current value. Returns undefined if unset, and
* throws if the field type does not include options.
*/
get chosenOption(): TypeName extends DataFavroCustomFieldTypeWithChoices
? { customFieldItemId: string; name: string } | undefined
get chosenOptions(): TypeName extends DataFavroCustomFieldTypeWithChoices
? { customFieldItemId: string; name: string }[]
: never {
const { type: fieldType, isSet } = this;
assertBravoClaim(
Expand All @@ -158,20 +163,22 @@ export class BravoCustomField<TypeName extends DataFavroCustomFieldType> {
);
if (!isSet) {
// @ts-expect-error
return;
return [];
}
// @ts-expect-error
return this.customFieldItems!.find(
(i) => i.customFieldItemId === (this.value as unknown as string),
)!;
return (this.value as { customFieldId: string; value: string[] }).value.map(
(chosenId) =>
this.customFieldItems!.find(
(option) => option.customFieldItemId == chosenId,
)!,
);
}

get humanFriendlyValue():
| BravoHumanFriendlyFieldValues[TypeName]
| undefined {
const type = this.type;
const value = this.value;
console.log({ type, value });
if (value === undefined) {
return;
}
Expand Down Expand Up @@ -214,13 +221,13 @@ export class BravoCustomField<TypeName extends DataFavroCustomFieldType> {
return (value as DataFavroCustomFieldsValues['Members']).value;
case 'Tags':
// @ts-expect-error
return (value as DataFavroCustomFieldsValues['Tags']).value;
return this.chosenOptions;
case 'Single select':
// @ts-expect-error
return (value as DataFavroCustomFieldsValues['Single select']).value;
return this.chosenOptions[0];
case 'Multiple select':
// @ts-expect-error
return (value as DataFavroCustomFieldsValues['Multiple select']).value;
return this.chosenOptions;
default:
throw new BravoError(`Unknown custom field type: ${type}`);
}
Expand Down
45 changes: 41 additions & 4 deletions src/test/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,20 @@ const customFieldUniquenessTestType = 'Text';
const sandboxRoot = './sandbox';
const samplesRoot = './samples';

export class BravoTestError extends Error {
/**
* Some upstream tests cannot be skipped since the create
* dependencies. To allow skipping most tests during active
* development, set this to `true`
*/
const SKIP_SKIPPABLE = false;

function canSkip(test: Mocha.Context) {
if (SKIP_SKIPPABLE) {
test.skip();
}
}

class BravoTestError extends Error {
constructor(message: string) {
super(message);
this.name = 'BravoTestError';
Expand Down Expand Up @@ -95,14 +108,17 @@ async function getCustomFieldByType<FieldType extends DataFavroCustomFieldType>(
) {
const fields = await client.listCustomFieldDefinitions();
const field = await fields.find((field) => {
console.log('TYPE', field.type);
return field.type === type;
});
assertBravoTestClaim(field, `No ${type} custom field found`);
const onCard = await card.getCustomField<FieldType>(field.customFieldId);
assertBravoTestClaim(
onCard.isSet === expectToBeSet,
`Custom Field in unexpected state`,
`Custom Field not ${expectToBeSet ? 'set' : 'unset'}: ${JSON.stringify(
[onCard.value, onCard.humanFriendlyValue],
null,
2,
)}`,
);
return onCard;
}
Expand Down Expand Up @@ -198,11 +214,13 @@ describe('BravoClient', function () {
});

it('can list organizations', async function () {
canSkip(this);
const organizations = await client.listOrganizations();
expect(organizations.length).to.be.greaterThan(0);
});

it('can find a specific organization', async function () {
canSkip(this);
const org = await client.findOrganizationById(organizationId);
assertBravoTestClaim(org, 'Org not found');
assertBravoTestClaim(
Expand All @@ -221,6 +239,7 @@ describe('BravoClient', function () {
});

it('can find a specific user by email', async function () {
canSkip(this);
const me = await client.findMemberByEmail(myUserEmail)!;
expect(me).to.exist;
expect(me!.email).to.equal(myUserEmail);
Expand All @@ -233,6 +252,7 @@ describe('BravoClient', function () {
});

it('can find created collection', async function () {
canSkip(this);
const foundCollection = await client.findCollectionByName(
testCollectionName.toLocaleLowerCase(),
{ ignoreCase: true },
Expand All @@ -252,6 +272,7 @@ describe('BravoClient', function () {
});

it('can find created widget', async function () {
canSkip(this);
// Grab the first widget found
const widget = await testCollection.findWidgetByName(
testWidgetName.toLocaleLowerCase(),
Expand All @@ -269,6 +290,7 @@ describe('BravoClient', function () {
expect(testColumn).to.exist;
});
it('can find a created column', async function () {
canSkip(this);
const foundColumn = await testWidget.findColumnByName(testColumnName);
assertBravoTestClaim(foundColumn);
expect(foundColumn!.equals(testColumn)).to.be.true;
Expand All @@ -279,6 +301,7 @@ describe('BravoClient', function () {
expect(testCard).to.exist;
});
it('can fetch a created card', async function () {
canSkip(this);
this.timeout(8000);

const foundCard = await testWidget.findCardInstanceByName(testCardName);
Expand All @@ -301,6 +324,7 @@ describe('BravoClient', function () {
});

it("can update a card's column (status)", async function () {
canSkip(this);
// Add another column to the widget to ensure at least 2
await testWidget.createColumn(testColumnName + '2');

Expand Down Expand Up @@ -334,6 +358,7 @@ describe('BravoClient', function () {
});

it("can batch-update a card's built-in fields", async function () {
canSkip(this);
/**
* Must be able to set/unset all of:
* - ✔ name
Expand Down Expand Up @@ -402,12 +427,14 @@ describe('BravoClient', function () {
expect(testCard.assignments).to.be.empty;
});
it("can singly update a Card's built-in fields", async function () {
canSkip(this);
const newTitle = 'singleton update';
await testCard.setName(newTitle);
expect(testCard.name).to.equal(testCard.name);
});

it('can add attachment to a card (and remove it)', async function () {
canSkip(this);
const filename = 'hi.txt';
const attachedText = 'Hello World!';
const attachment = await client.addAttachmentToCardInstance(
Expand All @@ -432,6 +459,7 @@ describe('BravoClient', function () {
// CUSTOM FIELDS

it('can find unique custom fields by name or id', async function () {
canSkip(this);
// NOTE: requires manual creation of a unique Custom Text field named 'Unique Text Field'
const fields = await client.listCustomFieldDefinitions();
const uniqueFields = await fields.filter(
Expand All @@ -449,6 +477,7 @@ describe('BravoClient', function () {
});

it('can find non-unique custom fields by name', async function () {
canSkip(this);
// NOTE: requires manual creation of at least two Custom Text fields named 'Repeated Text Field'
const fields = await client.listCustomFieldDefinitions();
const nonUniqueFields = await fields.filter(
Expand All @@ -465,6 +494,7 @@ describe('BravoClient', function () {
});

it('can find unique custom fields by name from a Card (when not set)', async function () {
canSkip(this);
const field = await testCard.getCustomFieldByName(
customFieldUniqueName,
customFieldUniquenessTestType,
Expand All @@ -474,6 +504,7 @@ describe('BravoClient', function () {
});

it('fails when trying to find a non-unique field by name from a Card (when not set)', async function () {
canSkip(this);
await expectAsyncError(() =>
testCard.getCustomFieldByName(
customFieldRepeatedName,
Expand All @@ -483,13 +514,15 @@ describe('BravoClient', function () {
});

it('can update a Custom Text Field', async function () {
// canSkip(this);
const customField = await getCustomFieldByType(client, testCard, 'Text');
await testCard.setCustomText(customField, 'New Custom Field Text');
const updatedField = await testCard.getCustomField(customField);
expect(updatedField.humanFriendlyValue).to.equal('New Custom Field Text');
});

it('can update a Custom Link Field', async function () {
canSkip(this);
const customField = await getCustomFieldByType(client, testCard, 'Link');
const link = { url: 'https://www.google.com', text: 'Google!' };
await testCard.setCustomLink(customField, link.url, link.text);
Expand All @@ -499,6 +532,7 @@ describe('BravoClient', function () {
});

it('can update a Custom Date Field', async function () {
canSkip(this);
const customField = await getCustomFieldByType(client, testCard, 'Date');
const now = new Date();
await testCard.setCustomDate(customField, now);
Expand All @@ -509,6 +543,7 @@ describe('BravoClient', function () {
});

it('can update a Custom Number Field', async function () {
canSkip(this);
const customField = await getCustomFieldByType(
client,
testCard,
Expand All @@ -520,6 +555,7 @@ describe('BravoClient', function () {
});

it('can update a Custom Vote Field', async function () {
canSkip(this);
const customField = await getCustomFieldByType(
client,
testCard,
Expand All @@ -532,6 +568,7 @@ describe('BravoClient', function () {
});

it('can update a Custom Rating Field', async function () {
canSkip(this);
const customField = await getCustomFieldByType(
client,
testCard,
Expand All @@ -553,7 +590,7 @@ describe('BravoClient', function () {
assertBravoClaim(newStatusId, 'Should have a status ID');
await testCard.setCustomStatusByStatusId(customField, newStatusId);
const updatedField = await testCard.getCustomField(customField);
expect(updatedField.chosenOption?.customFieldItemId).to.equal(
expect(updatedField.chosenOptions[0].customFieldItemId).to.equal(
newStatusId,
);
});
Expand Down

0 comments on commit d34b2b6

Please sign in to comment.