mattermost-mobile-upstream/app/client/rest/properties.test.ts
Pablo Vélez 4d842026f6
Some checks failed
ci / lint-typecheck (push) Has been cancelled
ci / detox-lint-typecheck (push) Has been cancelled
ci / test (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Mm 70564 channel labels use in channel creation (#10146)
* MM-70516 - Add ability to edit channel attribute values from Channel Info

* Add API methods for patching property fields and setting channel attribute requirements

* Update channel attribute clearing behavior for required and non-required fields

* MM-70564 - use channel attributes on channel creation

* Fix undefined propertyValues and ternary formatting in channel attributes

* Fix spelling of "colour" to "color" in attribute chip tests and related comments

* Add visibility checks for channel attribute labels in tests

* Fix data-integrity, permission, and accessibility issues in channel attributes: scoped pruning, atomic writes, race serialization, required clear/stale-option guards

* Fix handling of null values in apiGetChannelAttributeValue and update test assertions for channel attributes

* Improve channel attribute editor sizing for long labels, keep Save visible, and prevent keyboard overlap on Android

* Address CodeRabbit feedback for nullable values, bottom-sheet state, async validation, and reliable E2E cleanup

* lock-safe field fetch, create-time gate recheck, scoped value pruning, and tappable header chips

* Address CodeRabbit feedback: drop queued attribute saves on channel switch and use page object selectors in E2E

* Respect the ChannelAttributesRequired flag on channel creation and enable it for channel attribute E2E

* Skip required channel attribute E2E tests and stop enabling ChannelAttributesRequired on the shared server until  they move to SITE_3

* Fix clearing channel attributes on iOS by sending an empty value instead of null, and stop E2E suites from turning  off the ChannelAttributes flag mid-run

* Refactor getEmptyPropertyValue to accept string type for fieldType parameter

* E2E: give channel attribute test fields a suite-unique suffix

Pool shards may only create optional property fields named <name>_<6 hex>; fixed names were blocked by the pool guard.

* E2E: restore channel attribute cleanup checks; name network error codes

Brings back the PR's afterEach/afterAll error handling that the main merge replaced, and makes a no-response error show its codes (an AggregateError has an empty message).

* E2E: run channel attribute creation and editing specs on server B

Their 11 required-attribute tests could not run on the shared servers. Both specs now run on the mutation server with ChannelAttributesRequired on for their run, and the tests are re-enabled. setChannelAttributesRequired moves to the shared helper.

* Check header chips instead of their wrapper and use toExist for the create screen in channel attribute E2E

---------

Co-authored-by: yasserfaraazkhan <attitude3cena.yf@gmail.com>
2026-10-30 15:03:55 +01:00

166 lines
7.1 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import TestHelper from '@test/test_helper';
import type ClientBase from './base';
import type {ClientPropertiesMix} from './properties';
describe('ClientProperties', () => {
let client: ClientPropertiesMix & ClientBase;
beforeAll(() => {
client = TestHelper.createClient();
client.doFetch = jest.fn();
});
beforeEach(() => {
(client.doFetch as jest.Mock).mockReset();
});
test('getPropertyValues', async () => {
const groupName = 'managed_channel_categories';
const objectType = 'channel';
const targetId = 'channel_id_1';
const expectedUrl = `${client.urlVersion}/properties/groups/${groupName}/${objectType}/values/${targetId}`;
const expectedOptions = {method: 'get'};
await client.getPropertyValues<string>(groupName, objectType, targetId);
expect(client.doFetch).toHaveBeenCalledWith(expectedUrl, expectedOptions);
});
test('getPropertyFields', async () => {
const groupName = 'managed_channel_categories';
const objectType = 'channel';
const targetType = 'system';
const expectedUrl = `${client.urlVersion}/properties/groups/${groupName}/${objectType}/fields?target_type=${targetType}`;
const expectedOptions = {method: 'get'};
await client.getPropertyFields(groupName, objectType, targetType);
expect(client.doFetch).toHaveBeenCalledWith(expectedUrl, expectedOptions);
});
test('getPropertyFields should not append target_id when omitted', async () => {
(client.doFetch as jest.Mock).mockResolvedValueOnce([]);
await client.getPropertyFields('group', 'channel', 'system');
const url = (client.doFetch as jest.Mock).mock.calls[0][0] as string;
expect(url).not.toContain('target_id');
});
test('getPropertyFields should append target_id when provided', async () => {
(client.doFetch as jest.Mock).mockResolvedValueOnce([]);
await client.getPropertyFields('group', 'channel', 'system', 'tid');
const url = (client.doFetch as jest.Mock).mock.calls[0][0] as string;
expect(url).toContain('target_id=tid');
});
describe('patchPropertyValues', () => {
it('should PATCH the same URL the read uses, with a bare array body', async () => {
(client.doFetch as jest.Mock).mockResolvedValueOnce([]);
await client.patchPropertyValues<string>('access_control', 'channel', 'channel_id_1', [{field_id: 'field_id_1', value: 'option_id_1'}]);
expect(client.doFetch).toHaveBeenCalledWith(
`${client.urlVersion}/properties/groups/access_control/channel/values/channel_id_1`,
{method: 'patch', body: [{field_id: 'field_id_1', value: 'option_id_1'}]},
);
});
it('should send a null value through as a clear rather than dropping the key', async () => {
(client.doFetch as jest.Mock).mockResolvedValueOnce([]);
await client.patchPropertyValues<string>('access_control', 'channel', 'channel_id_1', [{field_id: 'field_id_1', value: null}]);
const options = (client.doFetch as jest.Mock).mock.calls[0][1];
expect(options.body).toEqual([{field_id: 'field_id_1', value: null}]);
});
it('should return the upserted values', async () => {
const data = [{id: 'v1', field_id: 'field_id_1', value: 'option_id_1'}];
(client.doFetch as jest.Mock).mockResolvedValueOnce(data);
const result = await client.patchPropertyValues<string>('access_control', 'channel', 'channel_id_1', [{field_id: 'field_id_1', value: 'option_id_1'}]);
expect(result).toEqual(data);
});
it('should return a null-valued row after clearing a value', async () => {
const data = [{id: 'v1', field_id: 'field_id_1', value: null}];
(client.doFetch as jest.Mock).mockResolvedValueOnce(data);
const result = await client.patchPropertyValues<string>('access_control', 'channel', 'channel_id_1', [{field_id: 'field_id_1', value: null}]);
expect(result).toEqual(data);
});
it('should propagate a rejection, so a 403 from a change policy reaches the caller', async () => {
(client.doFetch as jest.Mock).mockRejectedValueOnce({status_code: 403, message: 'change policy does not permit this change'});
await expect(client.patchPropertyValues<string>('access_control', 'channel', 'channel_id_1', [{field_id: 'field_id_1', value: 'option_id_1'}])).
rejects.toMatchObject({status_code: 403});
});
it('should return [] when doFetch resolves to a non-array', async () => {
(client.doFetch as jest.Mock).mockResolvedValueOnce({});
const result = await client.patchPropertyValues<string>('g', 'channel', 't', [{field_id: 'f', value: 'v'}]);
expect(result).toEqual([]);
});
});
describe('should return [] when doFetch resolves to a non-array', () => {
it('should return [] for getPropertyValues', async () => {
(client.doFetch as jest.Mock).mockResolvedValueOnce({});
const result = await client.getPropertyValues<string>('g', 'o', 't');
expect(result).toEqual([]);
});
it('should return [] for getPropertyFields', async () => {
(client.doFetch as jest.Mock).mockResolvedValueOnce({});
const result = await client.getPropertyFields('g', 'o', 't');
expect(result).toEqual([]);
});
it('should return [] for searchPropertyFields', async () => {
(client.doFetch as jest.Mock).mockResolvedValueOnce({});
const result = await client.searchPropertyFields('g', {object_types: []});
expect(result).toEqual([]);
});
it('should return [] for getSystemPropertyValues', async () => {
(client.doFetch as jest.Mock).mockResolvedValueOnce({});
const result = await client.getSystemPropertyValues<string>('g');
expect(result).toEqual([]);
});
it('should return [] for getPropertyValues when doFetch resolves to null', async () => {
(client.doFetch as jest.Mock).mockResolvedValueOnce(null);
const result = await client.getPropertyValues<string>('g', 'o', 't');
expect(result).toEqual([]);
});
});
describe('should pass through valid arrays', () => {
it('should return the array for getPropertyValues', async () => {
const data = [{id: 'v1'}];
(client.doFetch as jest.Mock).mockResolvedValueOnce(data);
const result = await client.getPropertyValues<string>('g', 'o', 't');
expect(result).toEqual(data);
});
it('should return the array for getPropertyFields', async () => {
const data = [{id: 'f1'}];
(client.doFetch as jest.Mock).mockResolvedValueOnce(data);
const result = await client.getPropertyFields('g', 'o', 't');
expect(result).toEqual(data);
});
});
});