mirror of
https://github.com/mattermost/mattermost-mobile.git
synced 2026-11-11 13:40:12 +00:00
* 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>
166 lines
7.1 KiB
TypeScript
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);
|
|
});
|
|
});
|
|
});
|