Merge branch 'MM-50966-in-product-expansion' of github.com:mattermost/mattermost-server into MM-50966-in-product-expansion
Этот коммит содержится в:
4
.github/workflows/channels-ci.yml
поставляемый
4
.github/workflows/channels-ci.yml
поставляемый
@@ -7,7 +7,7 @@ on:
|
|||||||
- mono-repo*
|
- mono-repo*
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: ${{ !contains( github.ref , 'heads/ref/master') }}
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -150,7 +150,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
NODE_OPTIONS: --max_old_space_size=5120
|
NODE_OPTIONS: --max_old_space_size=5120
|
||||||
run: |
|
run: |
|
||||||
# npm run test-ci --workspace=boards
|
npm run test-ci --workspace=boards
|
||||||
npm run test-ci --workspace=channels
|
npm run test-ci --workspace=channels
|
||||||
npm run test-ci --workspace=platform/client
|
npm run test-ci --workspace=platform/client
|
||||||
npm run test-ci --workspace=playbooks
|
npm run test-ci --workspace=playbooks
|
||||||
|
|||||||
2
.github/workflows/ci.yml
поставляемый
2
.github/workflows/ci.yml
поставляемый
@@ -11,7 +11,7 @@ env:
|
|||||||
go-version: "1.19.5"
|
go-version: "1.19.5"
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: ${{ !contains( github.ref , 'heads/ref/master') }}
|
||||||
jobs:
|
jobs:
|
||||||
check-mocks:
|
check-mocks:
|
||||||
name: Check mocks
|
name: Check mocks
|
||||||
|
|||||||
26
.github/workflows/codeql-analysis.yml
поставляемый
26
.github/workflows/codeql-analysis.yml
поставляемый
@@ -2,7 +2,7 @@ name: "CodeQL"
|
|||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: ${{ !contains( github.ref , 'heads/ref/master') }}
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
@@ -10,7 +10,7 @@ on:
|
|||||||
branches: [ master ]
|
branches: [ master ]
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '30 5,17 * * *'
|
- cron: '30 5,17 * * *'
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
language: [ 'go' ]
|
language: [ 'go', 'javascript' ]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
@@ -36,14 +36,26 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
debug: false
|
debug: false
|
||||||
config-file: ./.github/codeql/codeql-config.yml
|
config-file: ./.github/codeql/codeql-config.yml
|
||||||
|
|
||||||
- name: Build
|
- name: Build JavaScript
|
||||||
|
uses: github/codeql-action/autobuild@v2
|
||||||
|
if: ${{ matrix.language == 'javascript' }}
|
||||||
|
|
||||||
|
- name: Setup go
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: '1.20'
|
||||||
|
if: ${{ matrix.language == 'go' }}
|
||||||
|
|
||||||
|
|
||||||
|
- name: Build Golang
|
||||||
run: |
|
run: |
|
||||||
cd server
|
cd server
|
||||||
make setup-go-work
|
make setup-go-work
|
||||||
make build-linux-amd64
|
make build-linux-amd64
|
||||||
|
if: ${{ matrix.language == 'go' }}
|
||||||
|
|
||||||
# Perform Analysis
|
# Perform Analysis
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v2
|
uses: github/codeql-action/analyze@v2
|
||||||
|
|||||||
2
.github/workflows/e2e-tests-ci.yml
поставляемый
2
.github/workflows/e2e-tests-ci.yml
поставляемый
@@ -7,7 +7,7 @@ on:
|
|||||||
- mono-repo*
|
- mono-repo*
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: ${{ !contains( github.ref , 'heads/ref/master') }}
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
4
.github/workflows/scorecards-analysis.yml
поставляемый
4
.github/workflows/scorecards-analysis.yml
поставляемый
@@ -5,6 +5,10 @@ on:
|
|||||||
schedule:
|
schedule:
|
||||||
- cron: '44 6 * * *'
|
- cron: '44 6 * * *'
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: ${{ !contains( github.ref , 'heads/ref/master') }}
|
||||||
|
|
||||||
# Declare default permissions as read only.
|
# Declare default permissions as read only.
|
||||||
permissions: read-all
|
permissions: read-all
|
||||||
|
|
||||||
|
|||||||
1
.gitignore
поставляемый
1
.gitignore
поставляемый
@@ -134,6 +134,7 @@ cprofile.out
|
|||||||
*.test
|
*.test
|
||||||
webapp/coverage
|
webapp/coverage
|
||||||
/report.xml
|
/report.xml
|
||||||
|
junit.xml
|
||||||
|
|
||||||
.agignore
|
.agignore
|
||||||
.ctags
|
.ctags
|
||||||
|
|||||||
@@ -1 +1,9 @@
|
|||||||
/plugin/ @mattermost/toolkit
|
/plugin/ @mattermost/toolkit
|
||||||
|
|
||||||
|
/.github/workflows/channels-ci.yml @mattermost/web-platform
|
||||||
|
/webapp/package.json @mattermost/web-platform
|
||||||
|
/webapp/channels/package.json @mattermost/web-platform
|
||||||
|
/webapp/Makefile @mattermost/web-platform
|
||||||
|
/webapp/package-lock.json @mattermost/web-platform
|
||||||
|
/webapp/platform/*/package.json @mattermost/web-platform
|
||||||
|
/webapp/scripts @mattermost/web-platform
|
||||||
@@ -47,6 +47,57 @@ describe('Create and delete board / card', () => {
|
|||||||
should('have.value', 'Testing');
|
should('have.value', 'Testing');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('MM-T4275 Set up Board description', () => {
|
||||||
|
cy.visit('/boards');
|
||||||
|
|
||||||
|
// # Create an empty board and change tile to Testing
|
||||||
|
cy.findByText('Create an empty board').should('exist').click({force: true});
|
||||||
|
cy.get('.BoardComponent').should('exist');
|
||||||
|
|
||||||
|
// # Change Title
|
||||||
|
cy.findByPlaceholderText('Untitled board').should('be.visible').wait(timeouts.HALF_SEC);
|
||||||
|
|
||||||
|
// * Assert that the title is changed to "testing"
|
||||||
|
cy.findByPlaceholderText('Untitled board').
|
||||||
|
clear().
|
||||||
|
type('Testing').
|
||||||
|
type('{enter}').
|
||||||
|
should('have.value', 'Testing');
|
||||||
|
|
||||||
|
// # "Add icon" and "Show description" options appear
|
||||||
|
cy.findByText('Add icon').should('exist').click({force: true});
|
||||||
|
cy.findByText('show description').should('exist').click({force: true});
|
||||||
|
|
||||||
|
// # Click on "Add a description" below the board title and type "for testing purposes only"
|
||||||
|
cy.findByText('Add a description...').should('be.visible').wait(timeouts.HALF_SEC);
|
||||||
|
|
||||||
|
// * Assert that the editable description should be visible
|
||||||
|
cy.findByText('Add a description...').should('be.visible');
|
||||||
|
cy.findByText('Add a description...').click({force: true});
|
||||||
|
cy.get('.description').
|
||||||
|
click().
|
||||||
|
get('.description .MarkdownEditorInput').
|
||||||
|
type('for testing purposes only');
|
||||||
|
|
||||||
|
// # Click to other element to give some time for the description to be saved.
|
||||||
|
cy.findByPlaceholderText('Untitled board').click();
|
||||||
|
|
||||||
|
// * Assert that the description is changed to "for testing purposes only"
|
||||||
|
cy.findByText('for testing purposes only').should('be.visible');
|
||||||
|
|
||||||
|
// # Hide Description options should appear and click on it to hide description
|
||||||
|
cy.findByText('hide description').should('exist').click({force: true});
|
||||||
|
|
||||||
|
// * Assert that description should not appear"
|
||||||
|
cy.get('.description').should('not.exist');
|
||||||
|
|
||||||
|
// # Show Description options should appear and click on it to show description
|
||||||
|
cy.findByText('show description').should('exist').click({force: true});
|
||||||
|
|
||||||
|
// * Assert that the description "for testing purposes should be visible"
|
||||||
|
cy.findByText('for testing purposes only').should('be.visible');
|
||||||
|
});
|
||||||
|
|
||||||
it('MM-T5397 Can create and delete a board and a card', () => {
|
it('MM-T5397 Can create and delete a board and a card', () => {
|
||||||
// Visit a page and create new empty board
|
// Visit a page and create new empty board
|
||||||
cy.visit('/boards');
|
cy.visit('/boards');
|
||||||
|
|||||||
@@ -334,7 +334,7 @@ describe('Pricing modal', () => {
|
|||||||
|
|
||||||
// * Check that enterprise card action button shows Try free for 30 days
|
// * Check that enterprise card action button shows Try free for 30 days
|
||||||
cy.get('#pricingModal').should('be.visible');
|
cy.get('#pricingModal').should('be.visible');
|
||||||
cy.get('#start_cloud_trial_btn').contains('Try free for 30 days');
|
cy.get('#start_cloud_trial_btn').contains('Start trial');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should open pricing modal when Upgrade button clicked while in enterprise trial sku', () => {
|
it('should open pricing modal when Upgrade button clicked while in enterprise trial sku', () => {
|
||||||
@@ -365,7 +365,7 @@ describe('Pricing modal', () => {
|
|||||||
|
|
||||||
// * Check that enterprise card action button is disabled
|
// * Check that enterprise card action button is disabled
|
||||||
cy.get('#enterprise > .bottom > .bottom_container').should('be.visible');
|
cy.get('#enterprise > .bottom > .bottom_container').should('be.visible');
|
||||||
cy.get('#start_cloud_trial_btn').contains('Try free for 30 days');
|
cy.get('#start_cloud_trial_btn').contains('Start trial');
|
||||||
cy.get('#enterprise > .bottom > .bottom_container').should('be.visible');
|
cy.get('#enterprise > .bottom > .bottom_container').should('be.visible');
|
||||||
cy.get('#start_cloud_trial_btn').should('be.disabled');
|
cy.get('#start_cloud_trial_btn').should('be.disabled');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ describe('Self hosted Pricing modal', () => {
|
|||||||
// * Check that enteprise trial button is available
|
// * Check that enteprise trial button is available
|
||||||
cy.get('#pricingModal').should('be.visible');
|
cy.get('#pricingModal').should('be.visible');
|
||||||
cy.get('#enterprise').should('be.visible');
|
cy.get('#enterprise').should('be.visible');
|
||||||
cy.get('#start_trial_btn').should('not.be.disabled').contains('Try free for 30 days');
|
cy.get('#start_trial_btn').should('not.be.disabled').contains('Start trial');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Upgrade button should open pricing modal admin users when the server has requested a trial before on free plan', () => {
|
it('Upgrade button should open pricing modal admin users when the server has requested a trial before on free plan', () => {
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ describe('Feature discovery cloud', () => {
|
|||||||
|
|
||||||
const testForTrialButton = () => {
|
const testForTrialButton = () => {
|
||||||
cy.get('#start_cloud_trial_btn').should('exist');
|
cy.get('#start_cloud_trial_btn').should('exist');
|
||||||
cy.get('#start_cloud_trial_btn').contains('Try free for 30 days');
|
cy.get('#start_cloud_trial_btn').contains('Start trial');
|
||||||
};
|
};
|
||||||
|
|
||||||
const testForUpgradeToProfessionalOption = () => {
|
const testForUpgradeToProfessionalOption = () => {
|
||||||
|
|||||||
@@ -693,7 +693,7 @@ const defaultServerConfig: AdminConfig = {
|
|||||||
WysiwygEditor: false,
|
WysiwygEditor: false,
|
||||||
PeopleProduct: false,
|
PeopleProduct: false,
|
||||||
ReduceOnBoardingTaskList: false,
|
ReduceOnBoardingTaskList: false,
|
||||||
OnboardingAutoShowLinkedBoard: true,
|
OnboardingAutoShowLinkedBoard: false,
|
||||||
ThreadsEverywhere: false,
|
ThreadsEverywhere: false,
|
||||||
GlobalDrafts: true,
|
GlobalDrafts: true,
|
||||||
OnboardingTourTips: true,
|
OnboardingTourTips: true,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, test} from '@e2e-support/test_fixture';
|
import {expect, test} from '@e2e-support/test_fixture';
|
||||||
import {duration, isSmallScreen, wait} from '@e2e-support/util';
|
import {isSmallScreen} from '@e2e-support/util';
|
||||||
|
|
||||||
test('Intro to channel as regular user', async ({pw, pages, browserName, viewport}, testInfo) => {
|
test('Intro to channel as regular user', async ({pw, pages, browserName, viewport}, testInfo) => {
|
||||||
// Create and sign in a new user
|
// Create and sign in a new user
|
||||||
@@ -17,10 +17,10 @@ test('Intro to channel as regular user', async ({pw, pages, browserName, viewpor
|
|||||||
await channelsPage.toBeVisible();
|
await channelsPage.toBeVisible();
|
||||||
|
|
||||||
// Wait for Boards' bot image to be loaded
|
// Wait for Boards' bot image to be loaded
|
||||||
await pw.shouldHaveFeatureFlag('OnboardingAutoShowLinkedBoard', true);
|
// await pw.shouldHaveFeatureFlag('OnboardingAutoShowLinkedBoard', true);
|
||||||
const boardsWelcomePost = await channelsPage.getFirstPost();
|
// const boardsWelcomePost = await channelsPage.getFirstPost();
|
||||||
await expect(await boardsWelcomePost.getProfileImage('boards')).toBeVisible();
|
// await expect(await boardsWelcomePost.getProfileImage('boards')).toBeVisible();
|
||||||
await wait(duration.one_sec);
|
// await wait(duration.one_sec);
|
||||||
|
|
||||||
// Wait for Playbooks icon to be loaded in App bar, except in iphone
|
// Wait for Playbooks icon to be loaded in App bar, except in iphone
|
||||||
if (!isSmallScreen(viewport)) {
|
if (!isSmallScreen(viewport)) {
|
||||||
|
|||||||
@@ -7812,7 +7812,23 @@ func (c *Client4) GetChannelMemberCountsByGroup(channelID string, includeTimezon
|
|||||||
return ch, BuildResponse(r), nil
|
return ch, BuildResponse(r), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client4) RequestTrialLicenseWithExtraFields(trialRequest *TrialLicenseRequest) (*Response, error) {
|
||||||
|
b, err := json.Marshal(trialRequest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, NewAppError("RequestTrialLicense", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := c.DoAPIPost("/trial-license", string(b))
|
||||||
|
if err != nil {
|
||||||
|
return BuildResponse(r), err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer closeBody(r)
|
||||||
|
return BuildResponse(r), nil
|
||||||
|
}
|
||||||
|
|
||||||
// RequestTrialLicense will request a trial license and install it in the server
|
// RequestTrialLicense will request a trial license and install it in the server
|
||||||
|
// DEPRECATED - USE RequestTrialLicenseWithExtraFields (this method remains for backwards compatibility)
|
||||||
func (c *Client4) RequestTrialLicense(users int) (*Response, error) {
|
func (c *Client4) RequestTrialLicense(users int) (*Response, error) {
|
||||||
b, err := json.Marshal(map[string]any{"users": users, "terms_accepted": true})
|
b, err := json.Marshal(map[string]any{"users": users, "terms_accepted": true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -51,9 +51,6 @@ type FeatureFlags struct {
|
|||||||
|
|
||||||
CommandPalette bool
|
CommandPalette bool
|
||||||
|
|
||||||
// Enable Boards as a product (multi-product architecture)
|
|
||||||
BoardsProduct bool
|
|
||||||
|
|
||||||
// A/B Test on posting a welcome message
|
// A/B Test on posting a welcome message
|
||||||
SendWelcomePost bool
|
SendWelcomePost bool
|
||||||
|
|
||||||
@@ -95,7 +92,6 @@ func (f *FeatureFlags) SetDefaults() {
|
|||||||
f.InsightsEnabled = true
|
f.InsightsEnabled = true
|
||||||
f.CommandPalette = false
|
f.CommandPalette = false
|
||||||
f.CallsEnabled = true
|
f.CallsEnabled = true
|
||||||
f.BoardsProduct = false
|
|
||||||
f.SendWelcomePost = true
|
f.SendWelcomePost = true
|
||||||
f.PostPriority = true
|
f.PostPriority = true
|
||||||
f.PeopleProduct = false
|
f.PeopleProduct = false
|
||||||
@@ -104,7 +100,7 @@ func (f *FeatureFlags) SetDefaults() {
|
|||||||
f.ThreadsEverywhere = false
|
f.ThreadsEverywhere = false
|
||||||
f.GlobalDrafts = true
|
f.GlobalDrafts = true
|
||||||
f.WysiwygEditor = false
|
f.WysiwygEditor = false
|
||||||
f.OnboardingAutoShowLinkedBoard = true
|
f.OnboardingAutoShowLinkedBoard = false
|
||||||
f.OnboardingTourTips = true
|
f.OnboardingTourTips = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -84,6 +84,48 @@ type TrialLicenseRequest struct {
|
|||||||
Users int `json:"users"`
|
Users int `json:"users"`
|
||||||
TermsAccepted bool `json:"terms_accepted"`
|
TermsAccepted bool `json:"terms_accepted"`
|
||||||
ReceiveEmailsAccepted bool `json:"receive_emails_accepted"`
|
ReceiveEmailsAccepted bool `json:"receive_emails_accepted"`
|
||||||
|
ContactName string `json:"contact_name"`
|
||||||
|
ContactEmail string `json:"contact_email"`
|
||||||
|
CompanyName string `json:"company_name"`
|
||||||
|
CompanyCountry string `json:"company_country"`
|
||||||
|
CompanySize string `json:"company_size"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// If any of the below fields are set, this is not a legacy request, and all fields should be validated
|
||||||
|
func (tlr *TrialLicenseRequest) IsLegacy() bool {
|
||||||
|
return tlr.CompanyCountry == "" && tlr.CompanyName == "" && tlr.CompanySize == "" && tlr.ContactName == ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tlr *TrialLicenseRequest) IsValid() bool {
|
||||||
|
if !tlr.TermsAccepted {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if tlr.Email == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if tlr.Users <= 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if tlr.CompanyCountry == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if tlr.CompanyName == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if tlr.CompanySize == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if tlr.ContactName == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
type Features struct {
|
type Features struct {
|
||||||
|
|||||||
@@ -200,6 +200,100 @@ func TestLicenseRecordPreSave(t *testing.T) {
|
|||||||
assert.NotZero(t, lr.CreateAt)
|
assert.NotZero(t, lr.CreateAt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIsLegacyTrialRequest(t *testing.T) {
|
||||||
|
legacyTr := &TrialLicenseRequest{
|
||||||
|
Email: "test@mattermost.com",
|
||||||
|
TermsAccepted: true,
|
||||||
|
SiteURL: "https://mattermost.com",
|
||||||
|
SiteName: "Mattermost",
|
||||||
|
Users: 100,
|
||||||
|
}
|
||||||
|
t.Run("legacy trial request", func(t *testing.T) {
|
||||||
|
assert.True(t, legacyTr.IsLegacy())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("legacy trial request with any non-legacy field set is not a legacy request", func(t *testing.T) {
|
||||||
|
legacyTr.CompanyCountry = "US"
|
||||||
|
assert.False(t, legacyTr.IsLegacy())
|
||||||
|
legacyTr.CompanyCountry = ""
|
||||||
|
legacyTr.CompanyName = "test company"
|
||||||
|
assert.False(t, legacyTr.IsLegacy())
|
||||||
|
legacyTr.CompanyName = ""
|
||||||
|
legacyTr.CompanySize = "50-100"
|
||||||
|
assert.False(t, legacyTr.IsLegacy())
|
||||||
|
legacyTr.CompanySize = ""
|
||||||
|
legacyTr.ContactName = "test user"
|
||||||
|
assert.False(t, legacyTr.IsLegacy())
|
||||||
|
legacyTr.ContactName = ""
|
||||||
|
assert.True(t, legacyTr.IsLegacy())
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrialLicenseRequestIsValid(t *testing.T) {
|
||||||
|
validTlr := &TrialLicenseRequest{
|
||||||
|
Email: "test@test.com",
|
||||||
|
Users: 100,
|
||||||
|
CompanyCountry: "US",
|
||||||
|
CompanyName: "Test Company",
|
||||||
|
CompanySize: "50-100",
|
||||||
|
ContactName: "Test User",
|
||||||
|
TermsAccepted: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
resetBaseRequest := func() {
|
||||||
|
validTlr = &TrialLicenseRequest{
|
||||||
|
Email: "test@test.com",
|
||||||
|
Users: 100,
|
||||||
|
CompanyCountry: "US",
|
||||||
|
CompanyName: "Test Company",
|
||||||
|
CompanySize: "50-100",
|
||||||
|
ContactName: "Test User",
|
||||||
|
TermsAccepted: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Run("valid request", func(t *testing.T) {
|
||||||
|
resetBaseRequest()
|
||||||
|
assert.Equal(t, true, validTlr.IsValid())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("no terms", func(t *testing.T) {
|
||||||
|
resetBaseRequest()
|
||||||
|
validTlr.TermsAccepted = false
|
||||||
|
assert.Equal(t, false, validTlr.IsValid())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("no email", func(t *testing.T) {
|
||||||
|
resetBaseRequest()
|
||||||
|
validTlr.Email = ""
|
||||||
|
assert.Equal(t, false, validTlr.IsValid())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("no CompanyCountry", func(t *testing.T) {
|
||||||
|
resetBaseRequest()
|
||||||
|
validTlr.CompanyCountry = ""
|
||||||
|
assert.Equal(t, false, validTlr.IsValid())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("no CompanyName", func(t *testing.T) {
|
||||||
|
resetBaseRequest()
|
||||||
|
validTlr.CompanyName = ""
|
||||||
|
assert.Equal(t, false, validTlr.IsValid())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("no CompanySize", func(t *testing.T) {
|
||||||
|
resetBaseRequest()
|
||||||
|
validTlr.CompanySize = ""
|
||||||
|
assert.Equal(t, false, validTlr.IsValid())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Bad User Count", func(t *testing.T) {
|
||||||
|
resetBaseRequest()
|
||||||
|
validTlr.Users = 0
|
||||||
|
assert.Equal(t, false, validTlr.IsValid())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestLicense_IsTrialLicense(t *testing.T) {
|
func TestLicense_IsTrialLicense(t *testing.T) {
|
||||||
t.Run("detect trial license directly from the flag", func(t *testing.T) {
|
t.Run("detect trial license directly from the flag", func(t *testing.T) {
|
||||||
license := &License{
|
license := &License{
|
||||||
|
|||||||
@@ -118,9 +118,6 @@ var BuildDate string
|
|||||||
var BuildHash string
|
var BuildHash string
|
||||||
var BuildHashEnterprise string
|
var BuildHashEnterprise string
|
||||||
var BuildEnterpriseReady string
|
var BuildEnterpriseReady string
|
||||||
var BuildHashBoards string
|
|
||||||
var BuildBoards string
|
|
||||||
var BuildHashPlaybooks string
|
|
||||||
var versionsWithoutHotFixes []string
|
var versionsWithoutHotFixes []string
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
@@ -86,15 +86,6 @@ else
|
|||||||
BUILD_CLIENT = false
|
BUILD_CLIENT = false
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# Boards
|
|
||||||
BUILD_BOARDS = true
|
|
||||||
BUILD_HASH_BOARDS = $(BUILD_HASH)
|
|
||||||
export MM_FEATUREFLAGS_BoardsProduct=true
|
|
||||||
|
|
||||||
# Playbooks
|
|
||||||
BUILD_PLAYBOOKS ?= true
|
|
||||||
BUILD_HASH_PLAYBOOKS = $(BUILD_HASH)
|
|
||||||
|
|
||||||
# We need current user's UID for `run-haserver` so docker compose does not run server
|
# We need current user's UID for `run-haserver` so docker compose does not run server
|
||||||
# as root and mess up file permissions for devs. When running like this HOME will be blank
|
# as root and mess up file permissions for devs. When running like this HOME will be blank
|
||||||
# and docker will add '/', so we need to set the go-build cache location or we'll get
|
# and docker will add '/', so we need to set the go-build cache location or we'll get
|
||||||
@@ -116,9 +107,6 @@ LDFLAGS += -X "github.com/mattermost/mattermost-server/v6/model.BuildDate=$(BUIL
|
|||||||
LDFLAGS += -X "github.com/mattermost/mattermost-server/v6/model.BuildHash=$(BUILD_HASH)"
|
LDFLAGS += -X "github.com/mattermost/mattermost-server/v6/model.BuildHash=$(BUILD_HASH)"
|
||||||
LDFLAGS += -X "github.com/mattermost/mattermost-server/v6/model.BuildHashEnterprise=$(BUILD_HASH_ENTERPRISE)"
|
LDFLAGS += -X "github.com/mattermost/mattermost-server/v6/model.BuildHashEnterprise=$(BUILD_HASH_ENTERPRISE)"
|
||||||
LDFLAGS += -X "github.com/mattermost/mattermost-server/v6/model.BuildEnterpriseReady=$(BUILD_ENTERPRISE_READY)"
|
LDFLAGS += -X "github.com/mattermost/mattermost-server/v6/model.BuildEnterpriseReady=$(BUILD_ENTERPRISE_READY)"
|
||||||
LDFLAGS += -X "github.com/mattermost/mattermost-server/v6/model.BuildHashBoards=$(BUILD_HASH_BOARDS)"
|
|
||||||
LDFLAGS += -X "github.com/mattermost/mattermost-server/v6/model.BuildBoards=$(BUILD_BOARDS)"
|
|
||||||
LDFLAGS += -X "github.com/mattermost/mattermost-server/v6/model.BuildHashPlaybooks=$(BUILD_HASH_PLAYBOOKS)"
|
|
||||||
|
|
||||||
GO_MAJOR_VERSION = $(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f1)
|
GO_MAJOR_VERSION = $(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f1)
|
||||||
GO_MINOR_VERSION = $(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f2)
|
GO_MINOR_VERSION = $(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f2)
|
||||||
@@ -462,9 +450,9 @@ else
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
test-server-race: test-server-pre
|
test-server-race: test-server-pre
|
||||||
MM_DISABLE_PLAYBOOKS=true MM_FEATUREFLAGS_BoardsProduct=false ./scripts/test.sh "$(GO)" "-race $(GOFLAGS)" "$(TE_PACKAGES) $(EE_PACKAGES)" "$(TESTS)" "$(TESTFLAGS)" "$(GOBIN)" "90m" "atomic"
|
MM_DISABLE_PLAYBOOKS=true MM_DISABLE_BOARDS=true ./scripts/test.sh "$(GO)" "-race $(GOFLAGS)" "$(TE_PACKAGES) $(EE_PACKAGES)" "$(TESTS)" "$(TESTFLAGS)" "$(GOBIN)" "90m" "atomic"
|
||||||
MM_DISABLE_PLAYBOOKS=true MM_FEATUREFLAGS_BoardsProduct=true ./scripts/test.sh "$(GO)" "-race $(GOFLAGS)" "$(BOARDS_PACKAGES)" "$(TESTS)" "$(TESTFLAGS)" "$(GOBIN)" "90m" "atomic"
|
MM_DISABLE_PLAYBOOKS=true MM_DISABLE_BOARDS=false ./scripts/test.sh "$(GO)" "-race $(GOFLAGS)" "$(BOARDS_PACKAGES)" "$(TESTS)" "$(TESTFLAGS)" "$(GOBIN)" "90m" "atomic"
|
||||||
MM_DISABLE_PLAYBOOKS=false MM_FEATUREFLAGS_BoardsProduct=false ./scripts/test.sh "$(GO)" "-race $(GOFLAGS)" "$(PLAYBOOKS_PACKAGES)" "$(TESTS)" "$(TESTFLAGS)" "$(GOBIN)" "90m" "atomic"
|
MM_DISABLE_PLAYBOOKS=false MM_DISABLE_BOARDS=true ./scripts/test.sh "$(GO)" "-race $(GOFLAGS)" "$(PLAYBOOKS_PACKAGES)" "$(TESTS)" "$(TESTFLAGS)" "$(GOBIN)" "90m" "atomic"
|
||||||
ifneq ($(IS_CI),true)
|
ifneq ($(IS_CI),true)
|
||||||
ifneq ($(MM_NO_DOCKER),true)
|
ifneq ($(MM_NO_DOCKER),true)
|
||||||
ifneq ($(TEMP_DOCKER_SERVICES),)
|
ifneq ($(TEMP_DOCKER_SERVICES),)
|
||||||
@@ -475,9 +463,9 @@ ifneq ($(IS_CI),true)
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
test-server: test-server-pre
|
test-server: test-server-pre
|
||||||
MM_DISABLE_PLAYBOOKS=true MM_FEATUREFLAGS_BoardsProduct=false ./scripts/test.sh "$(GO)" "$(GOFLAGS)" "$(TE_PACKAGES) $(EE_PACKAGES)" "$(TESTS)" "$(TESTFLAGS)" "$(GOBIN)" "45m" "count"
|
MM_DISABLE_PLAYBOOKS=true MM_DISABLE_BOARDS=true ./scripts/test.sh "$(GO)" "$(GOFLAGS)" "$(TE_PACKAGES) $(EE_PACKAGES)" "$(TESTS)" "$(TESTFLAGS)" "$(GOBIN)" "45m" "count"
|
||||||
MM_DISABLE_PLAYBOOKS=true MM_FEATUREFLAGS_BoardsProduct=true ./scripts/test.sh "$(GO)" "$(GOFLAGS)" "$(BOARDS_PACKAGES)" "$(TESTS)" "$(TESTFLAGS)" "$(GOBIN)" "45m" "count"
|
MM_DISABLE_PLAYBOOKS=true MM_DISABLE_BOARDS=false ./scripts/test.sh "$(GO)" "$(GOFLAGS)" "$(BOARDS_PACKAGES)" "$(TESTS)" "$(TESTFLAGS)" "$(GOBIN)" "45m" "count"
|
||||||
MM_DISABLE_PLAYBOOKS=false MM_FEATUREFLAGS_BoardsProduct=false ./scripts/test.sh "$(GO)" "$(GOFLAGS)" "$(PLAYBOOKS_PACKAGES)" "$(TESTS)" "$(TESTFLAGS)" "$(GOBIN)" "45m" "count"
|
MM_DISABLE_PLAYBOOKS=false MM_DISABLE_BOARDS=true ./scripts/test.sh "$(GO)" "$(GOFLAGS)" "$(PLAYBOOKS_PACKAGES)" "$(TESTS)" "$(TESTFLAGS)" "$(GOBIN)" "45m" "count"
|
||||||
ifneq ($(IS_CI),true)
|
ifneq ($(IS_CI),true)
|
||||||
ifneq ($(MM_NO_DOCKER),true)
|
ifneq ($(MM_NO_DOCKER),true)
|
||||||
ifneq ($(TEMP_DOCKER_SERVICES),)
|
ifneq ($(TEMP_DOCKER_SERVICES),)
|
||||||
@@ -489,19 +477,19 @@ endif
|
|||||||
|
|
||||||
test-server-ee: check-prereqs-enterprise start-docker go-junit-report do-cover-file ## Runs EE tests.
|
test-server-ee: check-prereqs-enterprise start-docker go-junit-report do-cover-file ## Runs EE tests.
|
||||||
@echo Running only EE tests
|
@echo Running only EE tests
|
||||||
MM_DISABLE_PLAYBOOKS=true MM_FEATUREFLAGS_BoardsProduct=false ./scripts/test.sh "$(GO)" "$(GOFLAGS)" "$(EE_PACKAGES)" "$(TESTS)" "$(TESTFLAGS)" "$(GOBIN)" "20m" "count"
|
MM_DISABLE_PLAYBOOKS=true MM_DISABLE_BOARDS=true ./scripts/test.sh "$(GO)" "$(GOFLAGS)" "$(EE_PACKAGES)" "$(TESTS)" "$(TESTFLAGS)" "$(GOBIN)" "20m" "count"
|
||||||
|
|
||||||
test-server-quick: check-prereqs-enterprise ## Runs only quick tests.
|
test-server-quick: check-prereqs-enterprise ## Runs only quick tests.
|
||||||
ifeq ($(BUILD_ENTERPRISE_READY),true)
|
ifeq ($(BUILD_ENTERPRISE_READY),true)
|
||||||
@echo Running all tests
|
@echo Running all tests
|
||||||
MM_DISABLE_PLAYBOOKS=true MM_FEATUREFLAGS_BoardsProduct=false $(GO) test $(GOFLAGS) -short $(TE_PACKAGES) $(EE_PACKAGES)
|
MM_DISABLE_PLAYBOOKS=true MM_DISABLE_BOARDS=true $(GO) test $(GOFLAGS) -short $(TE_PACKAGES) $(EE_PACKAGES)
|
||||||
MM_DISABLE_PLAYBOOKS=true MM_FEATUREFLAGS_BoardsProduct=true $(GO) test $(GOFLAGS) -short $(BOARDS_PACKAGES)
|
MM_DISABLE_PLAYBOOKS=true MM_DISABLE_BOARDS=false $(GO) test $(GOFLAGS) -short $(BOARDS_PACKAGES)
|
||||||
MM_DISABLE_PLAYBOOKS=false MM_FEATUREFLAGS_BoardsProduct=false $(GO) test $(GOFLAGS) -short $(PLAYBOOKS_PACKAGES)
|
MM_DISABLE_PLAYBOOKS=false MM_DISABLE_BOARDS=true $(GO) test $(GOFLAGS) -short $(PLAYBOOKS_PACKAGES)
|
||||||
else
|
else
|
||||||
@echo Running only TE tests
|
@echo Running only TE tests
|
||||||
MM_DISABLE_PLAYBOOKS=true MM_FEATUREFLAGS_BoardsProduct=false $(GO) test $(GOFLAGS) -short $(TE_PACKAGES)
|
MM_DISABLE_PLAYBOOKS=true MM_DISABLE_BOARDS=true $(GO) test $(GOFLAGS) -short $(TE_PACKAGES)
|
||||||
MM_DISABLE_PLAYBOOKS=true MM_FEATUREFLAGS_BoardsProduct=true $(GO) test $(GOFLAGS) -short $(BOARDS_PACKAGES)
|
MM_DISABLE_PLAYBOOKS=true MM_DISABLE_BOARDS=false $(GO) test $(GOFLAGS) -short $(BOARDS_PACKAGES)
|
||||||
MM_DISABLE_PLAYBOOKS=false MM_FEATUREFLAGS_BoardsProduct=false $(GO) test $(GOFLAGS) -short $(PLAYBOOKS_PACKAGES)
|
MM_DISABLE_PLAYBOOKS=false MM_DISABLE_BOARDS=true $(GO) test $(GOFLAGS) -short $(PLAYBOOKS_PACKAGES)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
internal-test-web-client: ## Runs web client tests.
|
internal-test-web-client: ## Runs web client tests.
|
||||||
|
|||||||
@@ -222,11 +222,6 @@ func populateServices(boardsProd *boardsProduct, services map[product.ServiceKey
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (bp *boardsProduct) Start() error {
|
func (bp *boardsProduct) Start() error {
|
||||||
if !bp.configService.Config().FeatureFlags.BoardsProduct {
|
|
||||||
bp.logger.Info("Boards product disabled via feature flag")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
bp.logger.Info("Starting boards service")
|
bp.logger.Info("Starting boards service")
|
||||||
|
|
||||||
adapter := newServiceAPIAdapter(bp)
|
adapter := newServiceAPIAdapter(bp)
|
||||||
|
|||||||
@@ -80,12 +80,8 @@ func CreateBoardsConfig(mmconfig mm_model.Config, baseURL string, serverID strin
|
|||||||
showFullName = *mmconfig.PrivacySettings.ShowFullName
|
showFullName = *mmconfig.PrivacySettings.ShowFullName
|
||||||
}
|
}
|
||||||
|
|
||||||
serverRoot := baseURL + "/plugins/focalboard"
|
|
||||||
if mmconfig.FeatureFlags.BoardsProduct {
|
|
||||||
serverRoot = baseURL + "/boards"
|
|
||||||
}
|
|
||||||
return &config.Configuration{
|
return &config.Configuration{
|
||||||
ServerRoot: serverRoot,
|
ServerRoot: baseURL + "/boards",
|
||||||
Port: -1,
|
Port: -1,
|
||||||
DBType: *mmconfig.SqlSettings.DriverName,
|
DBType: *mmconfig.SqlSettings.DriverName,
|
||||||
DBConfigString: *mmconfig.SqlSettings.DataSource,
|
DBConfigString: *mmconfig.SqlSettings.DataSource,
|
||||||
|
|||||||
@@ -60,7 +60,21 @@ func (api *API) InitCloud() {
|
|||||||
api.BaseRoutes.Cloud.Handle("/delete-workspace", api.APISessionRequired(selfServeDeleteWorkspace)).Methods(http.MethodDelete)
|
api.BaseRoutes.Cloud.Handle("/delete-workspace", api.APISessionRequired(selfServeDeleteWorkspace)).Methods(http.MethodDelete)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ensureCloudInterface(c *Context, where string) bool {
|
||||||
|
cloud := c.App.Cloud()
|
||||||
|
if cloud == nil {
|
||||||
|
c.Err = model.NewAppError(where, "api.server.cws.needs_enterprise_edition", nil, "", http.StatusBadRequest)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func getSubscription(c *Context, w http.ResponseWriter, r *http.Request) {
|
func getSubscription(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
ensured := ensureCloudInterface(c, "Api4.getSubscription")
|
||||||
|
if !ensured {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if !c.App.Channels().License().IsCloud() {
|
if !c.App.Channels().License().IsCloud() {
|
||||||
c.Err = model.NewAppError("Api4.getSubscription", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
c.Err = model.NewAppError("Api4.getSubscription", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
||||||
return
|
return
|
||||||
@@ -102,6 +116,10 @@ func getSubscription(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func changeSubscription(c *Context, w http.ResponseWriter, r *http.Request) {
|
func changeSubscription(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
ensured := ensureCloudInterface(c, "Api4.changeSubscription")
|
||||||
|
if !ensured {
|
||||||
|
return
|
||||||
|
}
|
||||||
userId := c.AppContext.Session().UserId
|
userId := c.AppContext.Session().UserId
|
||||||
|
|
||||||
if !c.App.Channels().License().IsCloud() {
|
if !c.App.Channels().License().IsCloud() {
|
||||||
@@ -176,6 +194,11 @@ func changeSubscription(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func requestCloudTrial(c *Context, w http.ResponseWriter, r *http.Request) {
|
func requestCloudTrial(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
ensured := ensureCloudInterface(c, "Api4.requestCloudTrial")
|
||||||
|
if !ensured {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if !c.App.Channels().License().IsCloud() {
|
if !c.App.Channels().License().IsCloud() {
|
||||||
c.Err = model.NewAppError("Api4.requestCloudTrial", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
c.Err = model.NewAppError("Api4.requestCloudTrial", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
||||||
return
|
return
|
||||||
@@ -218,13 +241,8 @@ func requestCloudTrial(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func validateBusinessEmail(c *Context, w http.ResponseWriter, r *http.Request) {
|
func validateBusinessEmail(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
if !c.App.Channels().License().IsCloud() {
|
ensured := ensureCloudInterface(c, "Api4.validateBusinessEmail")
|
||||||
c.Err = model.NewAppError("Api4.validateBusinessEmail", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
if !ensured {
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionSysconsoleWriteBilling) {
|
|
||||||
c.SetPermissionError(model.PermissionSysconsoleWriteBilling)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,6 +281,11 @@ func validateBusinessEmail(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func validateWorkspaceBusinessEmail(c *Context, w http.ResponseWriter, r *http.Request) {
|
func validateWorkspaceBusinessEmail(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
ensured := ensureCloudInterface(c, "Api4.validateWorkspaceBusinessEmail")
|
||||||
|
if !ensured {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if !c.App.Channels().License().IsCloud() {
|
if !c.App.Channels().License().IsCloud() {
|
||||||
c.Err = model.NewAppError("Api4.validateWorkspaceBusinessEmail", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
c.Err = model.NewAppError("Api4.validateWorkspaceBusinessEmail", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
||||||
return
|
return
|
||||||
@@ -309,6 +332,11 @@ func validateWorkspaceBusinessEmail(c *Context, w http.ResponseWriter, r *http.R
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getSelfHostedProducts(c *Context, w http.ResponseWriter, r *http.Request) {
|
func getSelfHostedProducts(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
ensured := ensureCloudInterface(c, "Api4.getSelfHostedProducts")
|
||||||
|
if !ensured {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
products, err := c.App.Cloud().GetSelfHostedProducts(c.AppContext.Session().UserId)
|
products, err := c.App.Cloud().GetSelfHostedProducts(c.AppContext.Session().UserId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Err = model.NewAppError("Api4.getSelfHostedProducts", "api.cloud.request_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
c.Err = model.NewAppError("Api4.getSelfHostedProducts", "api.cloud.request_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||||
@@ -343,6 +371,11 @@ func getSelfHostedProducts(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getCloudProducts(c *Context, w http.ResponseWriter, r *http.Request) {
|
func getCloudProducts(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
ensured := ensureCloudInterface(c, "Api4.getCloudProducts")
|
||||||
|
if !ensured {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if !c.App.Channels().License().IsCloud() {
|
if !c.App.Channels().License().IsCloud() {
|
||||||
c.Err = model.NewAppError("Api4.getCloudProducts", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
c.Err = model.NewAppError("Api4.getCloudProducts", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
||||||
return
|
return
|
||||||
@@ -384,6 +417,11 @@ func getCloudProducts(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getCloudLimits(c *Context, w http.ResponseWriter, r *http.Request) {
|
func getCloudLimits(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
ensured := ensureCloudInterface(c, "Api4.getCloudLimits")
|
||||||
|
if !ensured {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if !c.App.Channels().License().IsCloud() {
|
if !c.App.Channels().License().IsCloud() {
|
||||||
c.Err = model.NewAppError("Api4.getCloudLimits", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
c.Err = model.NewAppError("Api4.getCloudLimits", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
||||||
return
|
return
|
||||||
@@ -405,6 +443,11 @@ func getCloudLimits(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getCloudCustomer(c *Context, w http.ResponseWriter, r *http.Request) {
|
func getCloudCustomer(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
ensured := ensureCloudInterface(c, "Api4.getCloudCustomer")
|
||||||
|
if !ensured {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if !c.App.Channels().License().IsCloud() {
|
if !c.App.Channels().License().IsCloud() {
|
||||||
c.Err = model.NewAppError("Api4.getCloudCustomer", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
c.Err = model.NewAppError("Api4.getCloudCustomer", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
||||||
return
|
return
|
||||||
@@ -432,6 +475,11 @@ func getCloudCustomer(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// getLicenseSelfServeStatus makes check for the license in the CWS self-serve portal and establishes if the license is renewable, expandable etc.
|
// getLicenseSelfServeStatus makes check for the license in the CWS self-serve portal and establishes if the license is renewable, expandable etc.
|
||||||
func getLicenseSelfServeStatus(c *Context, w http.ResponseWriter, r *http.Request) {
|
func getLicenseSelfServeStatus(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
ensured := ensureCloudInterface(c, "Api4.getLicenseSelfServeStatus")
|
||||||
|
if !ensured {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageLicenseInformation) {
|
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageLicenseInformation) {
|
||||||
c.SetPermissionError(model.PermissionManageLicenseInformation)
|
c.SetPermissionError(model.PermissionManageLicenseInformation)
|
||||||
return
|
return
|
||||||
@@ -460,6 +508,11 @@ func getLicenseSelfServeStatus(c *Context, w http.ResponseWriter, r *http.Reques
|
|||||||
}
|
}
|
||||||
|
|
||||||
func updateCloudCustomer(c *Context, w http.ResponseWriter, r *http.Request) {
|
func updateCloudCustomer(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
ensured := ensureCloudInterface(c, "Api4.updateCloudCustomer")
|
||||||
|
if !ensured {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if !c.App.Channels().License().IsCloud() {
|
if !c.App.Channels().License().IsCloud() {
|
||||||
c.Err = model.NewAppError("Api4.updateCloudCustomer", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
c.Err = model.NewAppError("Api4.updateCloudCustomer", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
||||||
return
|
return
|
||||||
@@ -498,6 +551,11 @@ func updateCloudCustomer(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func updateCloudCustomerAddress(c *Context, w http.ResponseWriter, r *http.Request) {
|
func updateCloudCustomerAddress(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
ensured := ensureCloudInterface(c, "Api4.updateCloudCustomerAddress")
|
||||||
|
if !ensured {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if !c.App.Channels().License().IsCloud() {
|
if !c.App.Channels().License().IsCloud() {
|
||||||
c.Err = model.NewAppError("Api4.updateCloudCustomerAddress", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
c.Err = model.NewAppError("Api4.updateCloudCustomerAddress", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
||||||
return
|
return
|
||||||
@@ -536,6 +594,11 @@ func updateCloudCustomerAddress(c *Context, w http.ResponseWriter, r *http.Reque
|
|||||||
}
|
}
|
||||||
|
|
||||||
func createCustomerPayment(c *Context, w http.ResponseWriter, r *http.Request) {
|
func createCustomerPayment(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
ensured := ensureCloudInterface(c, "Api4.createCustomerPayment")
|
||||||
|
if !ensured {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if !c.App.Channels().License().IsCloud() {
|
if !c.App.Channels().License().IsCloud() {
|
||||||
c.Err = model.NewAppError("Api4.createCustomerPayment", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
c.Err = model.NewAppError("Api4.createCustomerPayment", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
||||||
return
|
return
|
||||||
@@ -567,6 +630,11 @@ func createCustomerPayment(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func confirmCustomerPayment(c *Context, w http.ResponseWriter, r *http.Request) {
|
func confirmCustomerPayment(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
ensured := ensureCloudInterface(c, "Api4.confirmCustomerPayment")
|
||||||
|
if !ensured {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if !c.App.Channels().License().IsCloud() {
|
if !c.App.Channels().License().IsCloud() {
|
||||||
c.Err = model.NewAppError("Api4.confirmCustomerPayment", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
c.Err = model.NewAppError("Api4.confirmCustomerPayment", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
||||||
return
|
return
|
||||||
@@ -604,6 +672,11 @@ func confirmCustomerPayment(c *Context, w http.ResponseWriter, r *http.Request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getInvoicesForSubscription(c *Context, w http.ResponseWriter, r *http.Request) {
|
func getInvoicesForSubscription(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
ensured := ensureCloudInterface(c, "Api4.getInvoicesForSubscription")
|
||||||
|
if !ensured {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if !c.App.Channels().License().IsCloud() {
|
if !c.App.Channels().License().IsCloud() {
|
||||||
c.Err = model.NewAppError("Api4.getInvoicesForSubscription", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
c.Err = model.NewAppError("Api4.getInvoicesForSubscription", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
||||||
return
|
return
|
||||||
@@ -630,6 +703,11 @@ func getInvoicesForSubscription(c *Context, w http.ResponseWriter, r *http.Reque
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getSubscriptionInvoicePDF(c *Context, w http.ResponseWriter, r *http.Request) {
|
func getSubscriptionInvoicePDF(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
ensured := ensureCloudInterface(c, "Api4.getSubscriptionInvoicePDF")
|
||||||
|
if !ensured {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if !c.App.Channels().License().IsCloud() {
|
if !c.App.Channels().License().IsCloud() {
|
||||||
c.Err = model.NewAppError("Api4.getSubscriptionInvoicePDF", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
c.Err = model.NewAppError("Api4.getSubscriptionInvoicePDF", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
||||||
return
|
return
|
||||||
@@ -665,6 +743,11 @@ func getSubscriptionInvoicePDF(c *Context, w http.ResponseWriter, r *http.Reques
|
|||||||
}
|
}
|
||||||
|
|
||||||
func handleCWSWebhook(c *Context, w http.ResponseWriter, r *http.Request) {
|
func handleCWSWebhook(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
ensured := ensureCloudInterface(c, "Api4.handleCWSWebhook")
|
||||||
|
if !ensured {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if !c.App.Channels().License().IsCloud() {
|
if !c.App.Channels().License().IsCloud() {
|
||||||
c.Err = model.NewAppError("Api4.handleCWSWebhook", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
c.Err = model.NewAppError("Api4.handleCWSWebhook", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
||||||
return
|
return
|
||||||
@@ -765,12 +848,12 @@ func handleCWSWebhook(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func handleCheckCWSConnection(c *Context, w http.ResponseWriter, r *http.Request) {
|
func handleCheckCWSConnection(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
cloud := c.App.Cloud()
|
ensured := ensureCloudInterface(c, "Api4.handleCheckCWSConnection")
|
||||||
if cloud == nil {
|
if !ensured {
|
||||||
c.Err = model.NewAppError("Api4.handleCWSHealthCheck", "api.server.cws.needs_enterprise_edition", nil, "", http.StatusBadRequest)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := cloud.CheckCWSConnection(c.AppContext.Session().UserId); err != nil {
|
|
||||||
|
if err := c.App.Cloud().CheckCWSConnection(c.AppContext.Session().UserId); err != nil {
|
||||||
c.Err = model.NewAppError("Api4.handleCWSHealthCheck", "api.server.cws.health_check.app_error", nil, "CWS Server is not available.", http.StatusInternalServerError)
|
c.Err = model.NewAppError("Api4.handleCWSHealthCheck", "api.server.cws.health_check.app_error", nil, "CWS Server is not available.", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -779,6 +862,11 @@ func handleCheckCWSConnection(c *Context, w http.ResponseWriter, r *http.Request
|
|||||||
}
|
}
|
||||||
|
|
||||||
func selfServeDeleteWorkspace(c *Context, w http.ResponseWriter, r *http.Request) {
|
func selfServeDeleteWorkspace(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
ensured := ensureCloudInterface(c, "Api4.selfServeDeleteWorkspace")
|
||||||
|
if !ensured {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
bodyBytes, err := io.ReadAll(r.Body)
|
bodyBytes, err := io.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Err = model.NewAppError("Api4.selfServeDeleteWorkspace", "api.cloud.app_error", nil, err.Error(), http.StatusBadRequest)
|
c.Err = model.NewAppError("Api4.selfServeDeleteWorkspace", "api.cloud.app_error", nil, err.Error(), http.StatusBadRequest)
|
||||||
|
|||||||
@@ -20,6 +20,15 @@ func Test_getCloudLimits(t *testing.T) {
|
|||||||
th := Setup(t).InitBasic()
|
th := Setup(t).InitBasic()
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
|
|
||||||
|
cloud := &mocks.CloudInterface{}
|
||||||
|
cloud.Mock.On("GetCloudLimits", mock.Anything).Return(nil, errors.New("Unable to get limits"))
|
||||||
|
|
||||||
|
cloudImpl := th.App.Srv().Cloud
|
||||||
|
defer func() {
|
||||||
|
th.App.Srv().Cloud = cloudImpl
|
||||||
|
}()
|
||||||
|
th.App.Srv().Cloud = cloud
|
||||||
|
|
||||||
th.App.Srv().RemoveLicense()
|
th.App.Srv().RemoveLicense()
|
||||||
|
|
||||||
th.Client.Login(th.BasicUser.Email, th.BasicUser.Password)
|
th.Client.Login(th.BasicUser.Email, th.BasicUser.Password)
|
||||||
@@ -34,6 +43,15 @@ func Test_getCloudLimits(t *testing.T) {
|
|||||||
th := Setup(t).InitBasic()
|
th := Setup(t).InitBasic()
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
|
|
||||||
|
cloud := &mocks.CloudInterface{}
|
||||||
|
cloud.Mock.On("GetCloudLimits", mock.Anything).Return(nil, errors.New("Unable to get limits"))
|
||||||
|
|
||||||
|
cloudImpl := th.App.Srv().Cloud
|
||||||
|
defer func() {
|
||||||
|
th.App.Srv().Cloud = cloudImpl
|
||||||
|
}()
|
||||||
|
th.App.Srv().Cloud = cloud
|
||||||
|
|
||||||
th.App.Srv().SetLicense(model.NewTestLicense())
|
th.App.Srv().SetLicense(model.NewTestLicense())
|
||||||
|
|
||||||
th.Client.Login(th.BasicUser.Email, th.BasicUser.Password)
|
th.Client.Login(th.BasicUser.Email, th.BasicUser.Password)
|
||||||
@@ -310,30 +328,6 @@ func Test_requestTrial(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Test_validateBusinessEmail(t *testing.T) {
|
func Test_validateBusinessEmail(t *testing.T) {
|
||||||
t.Run("Returns forbidden for non admin executors", func(t *testing.T) {
|
|
||||||
th := Setup(t).InitBasic()
|
|
||||||
defer th.TearDown()
|
|
||||||
|
|
||||||
th.Client.Login(th.BasicUser.Email, th.BasicUser.Password)
|
|
||||||
|
|
||||||
invalidEmail := model.ValidateBusinessEmailRequest{Email: "invalid@gmail.com"}
|
|
||||||
|
|
||||||
th.App.Srv().SetLicense(model.NewTestLicense("cloud"))
|
|
||||||
|
|
||||||
cloud := mocks.CloudInterface{}
|
|
||||||
|
|
||||||
cloud.Mock.On("ValidateBusinessEmail", th.SystemAdminUser.Id, invalidEmail.Email).Return(errors.New("invalid email"))
|
|
||||||
|
|
||||||
cloudImpl := th.App.Srv().Cloud
|
|
||||||
defer func() {
|
|
||||||
th.App.Srv().Cloud = cloudImpl
|
|
||||||
}()
|
|
||||||
th.App.Srv().Cloud = &cloud
|
|
||||||
|
|
||||||
res, err := th.Client.ValidateBusinessEmail(&invalidEmail)
|
|
||||||
require.Error(t, err)
|
|
||||||
require.Equal(t, http.StatusForbidden, res.StatusCode, "403")
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Returns forbidden for invalid business email", func(t *testing.T) {
|
t.Run("Returns forbidden for invalid business email", func(t *testing.T) {
|
||||||
th := Setup(t).InitBasic()
|
th := Setup(t).InitBasic()
|
||||||
|
|||||||
@@ -39,9 +39,8 @@ func (api *API) InitHostedCustomer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ensureSelfHostedAdmin(c *Context, where string) {
|
func ensureSelfHostedAdmin(c *Context, where string) {
|
||||||
cloud := c.App.Cloud()
|
ensured := ensureCloudInterface(c, where)
|
||||||
if cloud == nil {
|
if !ensured {
|
||||||
c.Err = model.NewAppError(where, "api.server.cws.needs_enterprise_edition", nil, "", http.StatusBadRequest)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -210,11 +210,7 @@ func requestTrialLicense(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var trialRequest struct {
|
var trialRequest *model.TrialLicenseRequest
|
||||||
Users int `json:"users"`
|
|
||||||
TermsAccepted bool `json:"terms_accepted"`
|
|
||||||
ReceiveEmailsAccepted bool `json:"receive_emails_accepted"`
|
|
||||||
}
|
|
||||||
|
|
||||||
b, readErr := io.ReadAll(r.Body)
|
b, readErr := io.ReadAll(r.Body)
|
||||||
if readErr != nil {
|
if readErr != nil {
|
||||||
@@ -223,8 +219,16 @@ func requestTrialLicense(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
json.Unmarshal(b, &trialRequest)
|
json.Unmarshal(b, &trialRequest)
|
||||||
|
|
||||||
if err := c.App.Channels().RequestTrialLicense(c.AppContext.Session().UserId, trialRequest.Users, trialRequest.TermsAccepted, trialRequest.ReceiveEmailsAccepted); err != nil {
|
var appErr *model.AppError
|
||||||
c.Err = err
|
// If any of the newly supported trial request fields are set (ie, not a legacy request), process this as a new trial request (requiring the new fields) otherwise fall back on the old method.
|
||||||
|
if !trialRequest.IsLegacy() {
|
||||||
|
appErr = c.App.Channels().RequestTrialLicenseWithExtraFields(c.AppContext.Session().UserId, trialRequest)
|
||||||
|
} else {
|
||||||
|
appErr = c.App.Channels().RequestTrialLicense(c.AppContext.Session().UserId, trialRequest.Users, trialRequest.TermsAccepted, trialRequest.ReceiveEmailsAccepted)
|
||||||
|
}
|
||||||
|
|
||||||
|
if appErr != nil {
|
||||||
|
c.Err = appErr
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -223,6 +223,154 @@ func TestRemoveLicenseFile(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRequestTrialLicenseWithExtraFields(t *testing.T) {
|
||||||
|
th := Setup(t)
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
licenseManagerMock := &mocks.LicenseInterface{}
|
||||||
|
licenseManagerMock.On("CanStartTrial").Return(true, nil)
|
||||||
|
th.App.Srv().Platform().SetLicenseManager(licenseManagerMock)
|
||||||
|
cloud := mocks.CloudInterface{}
|
||||||
|
|
||||||
|
cloudImpl := th.App.Srv().Cloud
|
||||||
|
defer func() {
|
||||||
|
th.App.Srv().Cloud = cloudImpl
|
||||||
|
}()
|
||||||
|
th.App.Srv().Cloud = &cloud
|
||||||
|
|
||||||
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.SiteURL = "http://localhost:8065/" })
|
||||||
|
nUsers := 1
|
||||||
|
validTrialRequest := &model.TrialLicenseRequest{
|
||||||
|
Email: "test@mattermost.com",
|
||||||
|
Users: nUsers,
|
||||||
|
TermsAccepted: true,
|
||||||
|
CompanyCountry: "US",
|
||||||
|
CompanyName: "mattermost",
|
||||||
|
CompanySize: "1-10",
|
||||||
|
ContactName: "Matter Most",
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("permission denied", func(t *testing.T) {
|
||||||
|
resp, err := th.Client.RequestTrialLicenseWithExtraFields(&model.TrialLicenseRequest{})
|
||||||
|
require.Error(t, err)
|
||||||
|
CheckForbiddenStatus(t, resp)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("trial license user count less than current users", func(t *testing.T) {
|
||||||
|
license := model.NewTestLicense()
|
||||||
|
license.Features.Users = model.NewInt(nUsers)
|
||||||
|
licenseJSON, jsonErr := json.Marshal(license)
|
||||||
|
require.NoError(t, jsonErr)
|
||||||
|
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
||||||
|
res.WriteHeader(http.StatusOK)
|
||||||
|
response := map[string]string{
|
||||||
|
"license": string(licenseJSON),
|
||||||
|
}
|
||||||
|
err := json.NewEncoder(res).Encode(response)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}))
|
||||||
|
defer testServer.Close()
|
||||||
|
|
||||||
|
mockLicenseValidator := mocks2.LicenseValidatorIface{}
|
||||||
|
defer testutils.ResetLicenseValidator()
|
||||||
|
|
||||||
|
mockLicenseValidator.On("ValidateLicense", mock.Anything).Return(true, string(licenseJSON))
|
||||||
|
utils.LicenseValidator = &mockLicenseValidator
|
||||||
|
licenseManagerMock := &mocks.LicenseInterface{}
|
||||||
|
licenseManagerMock.On("CanStartTrial").Return(true, nil).Once()
|
||||||
|
th.App.Srv().Platform().SetLicenseManager(licenseManagerMock)
|
||||||
|
originalCwsUrl := *th.App.Srv().Config().CloudSettings.CWSURL
|
||||||
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.CloudSettings.CWSURL = testServer.URL })
|
||||||
|
defer func(requestTrialURL string) {
|
||||||
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.CloudSettings.CWSURL = requestTrialURL })
|
||||||
|
}(originalCwsUrl)
|
||||||
|
|
||||||
|
cloud.On("ValidateBusinessEmail", mock.Anything, mock.Anything).Return(nil)
|
||||||
|
|
||||||
|
resp, err := th.SystemAdminClient.RequestTrialLicenseWithExtraFields(validTrialRequest)
|
||||||
|
CheckErrorID(t, err, "api.license.add_license.unique_users.app_error")
|
||||||
|
CheckBadRequestStatus(t, resp)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("returns status 451 when it receives status 451", func(t *testing.T) {
|
||||||
|
|
||||||
|
license := model.NewTestLicense()
|
||||||
|
license.Features.Users = model.NewInt(nUsers)
|
||||||
|
licenseJSON, jsonErr := json.Marshal(license)
|
||||||
|
require.NoError(t, jsonErr)
|
||||||
|
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
||||||
|
res.WriteHeader(http.StatusUnavailableForLegalReasons)
|
||||||
|
}))
|
||||||
|
defer testServer.Close()
|
||||||
|
|
||||||
|
mockLicenseValidator := mocks2.LicenseValidatorIface{}
|
||||||
|
defer testutils.ResetLicenseValidator()
|
||||||
|
|
||||||
|
mockLicenseValidator.On("ValidateLicense", mock.Anything).Return(true, string(licenseJSON))
|
||||||
|
utils.LicenseValidator = &mockLicenseValidator
|
||||||
|
licenseManagerMock := &mocks.LicenseInterface{}
|
||||||
|
licenseManagerMock.On("CanStartTrial").Return(true, nil).Once()
|
||||||
|
th.App.Srv().Platform().SetLicenseManager(licenseManagerMock)
|
||||||
|
|
||||||
|
originalCwsUrl := *th.App.Srv().Config().CloudSettings.CWSURL
|
||||||
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.CloudSettings.CWSURL = testServer.URL })
|
||||||
|
defer func(requestTrialURL string) {
|
||||||
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.CloudSettings.CWSURL = requestTrialURL })
|
||||||
|
}(originalCwsUrl)
|
||||||
|
|
||||||
|
resp, err := th.SystemAdminClient.RequestTrialLicenseWithExtraFields(validTrialRequest)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Equal(t, resp.StatusCode, 451)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("returns status 400 if request is a mix of legacy and new fields", func(t *testing.T) {
|
||||||
|
validTrialRequest.CompanyCountry = ""
|
||||||
|
validTrialRequest.Users = 100
|
||||||
|
defer func() { validTrialRequest.CompanyCountry = "US" }()
|
||||||
|
license := model.NewTestLicense()
|
||||||
|
license.Features.Users = model.NewInt(nUsers)
|
||||||
|
licenseJSON, jsonErr := json.Marshal(license)
|
||||||
|
require.NoError(t, jsonErr)
|
||||||
|
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
||||||
|
res.WriteHeader(http.StatusOK)
|
||||||
|
response := map[string]string{
|
||||||
|
"license": string(licenseJSON),
|
||||||
|
}
|
||||||
|
err := json.NewEncoder(res).Encode(response)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}))
|
||||||
|
defer testServer.Close()
|
||||||
|
|
||||||
|
mockLicenseValidator := mocks2.LicenseValidatorIface{}
|
||||||
|
defer testutils.ResetLicenseValidator()
|
||||||
|
|
||||||
|
mockLicenseValidator.On("ValidateLicense", mock.Anything).Return(true, string(licenseJSON))
|
||||||
|
utils.LicenseValidator = &mockLicenseValidator
|
||||||
|
licenseManagerMock := &mocks.LicenseInterface{}
|
||||||
|
licenseManagerMock.On("CanStartTrial").Return(true, nil).Once()
|
||||||
|
th.App.Srv().Platform().SetLicenseManager(licenseManagerMock)
|
||||||
|
originalCwsUrl := *th.App.Srv().Config().CloudSettings.CWSURL
|
||||||
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.CloudSettings.CWSURL = testServer.URL })
|
||||||
|
defer func(requestTrialURL string) {
|
||||||
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.CloudSettings.CWSURL = requestTrialURL })
|
||||||
|
}(originalCwsUrl)
|
||||||
|
|
||||||
|
cloud.On("ValidateBusinessEmail", mock.Anything, mock.Anything).Return(nil)
|
||||||
|
|
||||||
|
resp, err := th.SystemAdminClient.RequestTrialLicenseWithExtraFields(validTrialRequest)
|
||||||
|
CheckErrorID(t, err, "api.license.request-trial.bad-request")
|
||||||
|
CheckBadRequestStatus(t, resp)
|
||||||
|
})
|
||||||
|
|
||||||
|
th.App.Srv().Platform().SetLicenseManager(nil)
|
||||||
|
t.Run("trial license should fail if LicenseManager is nil", func(t *testing.T) {
|
||||||
|
resp, err := th.SystemAdminClient.RequestTrialLicenseWithExtraFields(validTrialRequest)
|
||||||
|
CheckErrorID(t, err, "api.license.upgrade_needed.app_error")
|
||||||
|
CheckForbiddenStatus(t, resp)
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func TestRequestTrialLicense(t *testing.T) {
|
func TestRequestTrialLicense(t *testing.T) {
|
||||||
th := Setup(t)
|
th := Setup(t)
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ const ServerKey product.ServiceKey = "server"
|
|||||||
type licenseSvc interface {
|
type licenseSvc interface {
|
||||||
GetLicense() *model.License
|
GetLicense() *model.License
|
||||||
RequestTrialLicense(requesterID string, users int, termsAccepted bool, receiveEmailsAccepted bool) *model.AppError
|
RequestTrialLicense(requesterID string, users int, termsAccepted bool, receiveEmailsAccepted bool) *model.AppError
|
||||||
|
RequestTrialLicenseWithExtraFields(requesterID string, trialRequest *model.TrialLicenseRequest) *model.AppError
|
||||||
}
|
}
|
||||||
|
|
||||||
// Channels contains all channels related state.
|
// Channels contains all channels related state.
|
||||||
@@ -323,6 +324,10 @@ func (ch *Channels) License() *model.License {
|
|||||||
return ch.licenseSvc.GetLicense()
|
return ch.licenseSvc.GetLicense()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ch *Channels) RequestTrialLicenseWithExtraFields(requesterID string, trialRequest *model.TrialLicenseRequest) *model.AppError {
|
||||||
|
return ch.licenseSvc.RequestTrialLicenseWithExtraFields(requesterID, trialRequest)
|
||||||
|
}
|
||||||
|
|
||||||
func (ch *Channels) RequestTrialLicense(requesterID string, users int, termsAccepted bool, receiveEmailsAccepted bool) *model.AppError {
|
func (ch *Channels) RequestTrialLicense(requesterID string, users int, termsAccepted bool, receiveEmailsAccepted bool) *model.AppError {
|
||||||
return ch.licenseSvc.RequestTrialLicense(requesterID, users, termsAccepted,
|
return ch.licenseSvc.RequestTrialLicense(requesterID, users, termsAccepted,
|
||||||
receiveEmailsAccepted)
|
receiveEmailsAccepted)
|
||||||
|
|||||||
@@ -36,6 +36,51 @@ func (w *licenseWrapper) GetLicense() *model.License {
|
|||||||
return w.srv.License()
|
return w.srv.License()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *licenseWrapper) RequestTrialLicenseWithExtraFields(requesterID string, trialRequest *model.TrialLicenseRequest) *model.AppError {
|
||||||
|
if *w.srv.platform.Config().ExperimentalSettings.RestrictSystemAdmin {
|
||||||
|
return model.NewAppError("RequestTrialLicense", "api.restricted_system_admin", nil, "", http.StatusForbidden)
|
||||||
|
}
|
||||||
|
|
||||||
|
requester, err := w.srv.userService.GetUser(requesterID)
|
||||||
|
if err != nil {
|
||||||
|
var nfErr *store.ErrNotFound
|
||||||
|
switch {
|
||||||
|
case errors.As(err, &nfErr):
|
||||||
|
return model.NewAppError("RequestTrialLicense", MissingAccountError, nil, "", http.StatusNotFound).Wrap(err)
|
||||||
|
default:
|
||||||
|
return model.NewAppError("RequestTrialLicense", "app.user.get_by_username.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if w.srv.Cloud.ValidateBusinessEmail(requesterID, trialRequest.ContactEmail) != nil {
|
||||||
|
return model.NewAppError("RequestTrialLicense", "api.license.request-trial.bad-request.business-email", nil, "", http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new struct only using the fields from the request that are allowed to be set by the client
|
||||||
|
sanitizedRequest := &model.TrialLicenseRequest{
|
||||||
|
ServerID: w.srv.TelemetryId(),
|
||||||
|
Name: requester.GetDisplayName(model.ShowFullName),
|
||||||
|
Email: requester.Email,
|
||||||
|
SiteName: *w.srv.platform.Config().TeamSettings.SiteName,
|
||||||
|
SiteURL: *w.srv.platform.Config().ServiceSettings.SiteURL,
|
||||||
|
Users: trialRequest.Users,
|
||||||
|
TermsAccepted: trialRequest.TermsAccepted,
|
||||||
|
ReceiveEmailsAccepted: trialRequest.ReceiveEmailsAccepted,
|
||||||
|
ContactName: trialRequest.ContactName,
|
||||||
|
ContactEmail: trialRequest.ContactEmail,
|
||||||
|
CompanyName: trialRequest.CompanyName,
|
||||||
|
CompanySize: trialRequest.CompanySize,
|
||||||
|
CompanyCountry: trialRequest.CompanyCountry,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !sanitizedRequest.IsValid() {
|
||||||
|
return model.NewAppError("RequestTrialLicense", "api.license.request-trial.bad-request", nil, "", http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
return w.srv.platform.RequestTrialLicense(sanitizedRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEPRECATED - use RequestTrialLicenseWithExtraFields instead. This function remains to support the Plugin API.
|
||||||
func (w *licenseWrapper) RequestTrialLicense(requesterID string, users int, termsAccepted bool, receiveEmailsAccepted bool) *model.AppError {
|
func (w *licenseWrapper) RequestTrialLicense(requesterID string, users int, termsAccepted bool, receiveEmailsAccepted bool) *model.AppError {
|
||||||
if *w.srv.platform.Config().ExperimentalSettings.RestrictSystemAdmin {
|
if *w.srv.platform.Config().ExperimentalSettings.RestrictSystemAdmin {
|
||||||
return model.NewAppError("RequestTrialLicense", "api.restricted_system_admin", nil, "", http.StatusForbidden)
|
return model.NewAppError("RequestTrialLicense", "api.restricted_system_admin", nil, "", http.StatusForbidden)
|
||||||
|
|||||||
@@ -363,6 +363,12 @@ func (a *App) getImagesForPost(c request.CTX, post *model.Post, imageURLs []stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, imageURL := range imageURLs {
|
for _, imageURL := range imageURLs {
|
||||||
|
// prevent infinite loop if a OG image URL is the same post's permalink
|
||||||
|
resolvedURL := resolveMetadataURL(imageURL, a.GetSiteURL())
|
||||||
|
if looksLikeAPermalink(resolvedURL, a.GetSiteURL()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if _, image, _, err := a.getLinkMetadata(c, imageURL, post.CreateAt, isNewPost, post.GetPreviewedPostProp()); err != nil {
|
if _, image, _, err := a.getLinkMetadata(c, imageURL, post.CreateAt, isNewPost, post.GetPreviewedPostProp()); err != nil {
|
||||||
appErr, ok := err.(*model.AppError)
|
appErr, ok := err.(*model.AppError)
|
||||||
isNotFound := ok && appErr.StatusCode == http.StatusNotFound
|
isNotFound := ok && appErr.StatusCode == http.StatusNotFound
|
||||||
@@ -651,6 +657,9 @@ func (a *App) getLinkMetadata(c request.CTX, requestURL string, timestamp int64,
|
|||||||
|
|
||||||
var res *http.Response
|
var res *http.Response
|
||||||
res, err = client.Do(request)
|
res, err = client.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
mlog.Warn("error fetching OG image data", mlog.Err(err))
|
||||||
|
}
|
||||||
|
|
||||||
if res != nil {
|
if res != nil {
|
||||||
body = res.Body
|
body = res.Body
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost-server/v6/server/channels/store"
|
||||||
|
"github.com/mattermost/mattermost-server/v6/server/channels/store/storetest/mocks"
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
|
|
||||||
"github.com/dyatlov/go-opengraph/opengraph"
|
"github.com/dyatlov/go-opengraph/opengraph"
|
||||||
ogimage "github.com/dyatlov/go-opengraph/opengraph/types/image"
|
ogimage "github.com/dyatlov/go-opengraph/opengraph/types/image"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -1311,6 +1315,49 @@ func TestGetImagesForPost(t *testing.T) {
|
|||||||
images := th.App.getImagesForPost(th.Context, post, []string{}, false)
|
images := th.App.getImagesForPost(th.Context, post, []string{}, false)
|
||||||
assert.Equal(t, images, map[string]*model.PostImage{})
|
assert.Equal(t, images, map[string]*model.PostImage{})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("should not process OpenGraph image that's a Mattermost permalink", func(t *testing.T) {
|
||||||
|
th := SetupWithStoreMock(t)
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
ogURL := "https://example.com/index.html"
|
||||||
|
imageURL := th.App.GetSiteURL() + "/pl/qwertyuiopasdfghjklzxcvbnm"
|
||||||
|
|
||||||
|
post := &model.Post{
|
||||||
|
Id: "qwertyuiopasdfghjklzxcvbnm",
|
||||||
|
Metadata: &model.PostMetadata{
|
||||||
|
Embeds: []*model.PostEmbed{
|
||||||
|
{
|
||||||
|
Type: model.PostEmbedOpengraph,
|
||||||
|
URL: ogURL,
|
||||||
|
Data: &opengraph.OpenGraph{
|
||||||
|
Images: []*ogimage.Image{
|
||||||
|
{
|
||||||
|
URL: imageURL,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
mockPostStore := mocks.PostStore{}
|
||||||
|
mockPostStore.On("GetSingle", "qwertyuiopasdfghjklzxcvbnm", false).RunFn = func(args mock.Arguments) {
|
||||||
|
assert.Fail(t, "should not have tried to process Mattermost permalink in OG image URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
mockLinkMetadataStore := mocks.LinkMetadataStore{}
|
||||||
|
mockLinkMetadataStore.On("Get", mock.Anything, mock.Anything).Return(nil, store.NewErrNotFound("mock resource", "mock ID"))
|
||||||
|
|
||||||
|
mockStore := th.App.Srv().Store().(*mocks.Store)
|
||||||
|
mockStore.On("Post").Return(&mockPostStore)
|
||||||
|
mockStore.On("LinkMetadata").Return(&mockLinkMetadataStore)
|
||||||
|
|
||||||
|
images := th.App.getImagesForPost(th.Context, post, []string{}, false)
|
||||||
|
assert.Equal(t, 0, len(images))
|
||||||
|
assert.Equal(t, images, map[string]*model.PostImage{})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetEmojiNamesForString(t *testing.T) {
|
func TestGetEmojiNamesForString(t *testing.T) {
|
||||||
|
|||||||
@@ -72,14 +72,14 @@ func (s *Server) initializeProducts(
|
|||||||
|
|
||||||
func (s *Server) shouldStart(product string) bool {
|
func (s *Server) shouldStart(product string) bool {
|
||||||
if product == "boards" {
|
if product == "boards" {
|
||||||
if !s.Config().FeatureFlags.BoardsProduct {
|
if os.Getenv("MM_DISABLE_BOARDS") == "true" {
|
||||||
s.Log().Warn("Skipping boards start: not enabled via feature flag")
|
s.Log().Warn("Skipping Boards start: disabled via env var")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if product == "playbooks" {
|
if product == "playbooks" {
|
||||||
if os.Getenv("MM_DISABLE_PLAYBOOKS") == "true" {
|
if os.Getenv("MM_DISABLE_PLAYBOOKS") == "true" {
|
||||||
s.Log().Warn("Skipping playbooks start: disabled via env var")
|
s.Log().Warn("Skipping Playbooks start: disabled via env var")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,6 +152,11 @@ var searchUserStoreTests = []searchTest{
|
|||||||
Fn: testSearchUsersInTeamUsernameWithUnderscore,
|
Fn: testSearchUsersInTeamUsernameWithUnderscore,
|
||||||
Tags: []string{EngineAll},
|
Tags: []string{EngineAll},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "Should support search all users containing a substring in any name",
|
||||||
|
Fn: testSearchUserBySubstringInAnyName,
|
||||||
|
Tags: []string{EngineAll},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSearchUserStore(t *testing.T, s store.Store, testEngine *SearchTestEngine) {
|
func TestSearchUserStore(t *testing.T, s store.Store, testEngine *SearchTestEngine) {
|
||||||
@@ -865,6 +870,76 @@ func testSearchUsersByFullName(t *testing.T, th *SearchTestHelper) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testSearchUserBySubstringInAnyName(t *testing.T, th *SearchTestHelper) {
|
||||||
|
t.Run("Should search users by substring in first name", func(t *testing.T) {
|
||||||
|
userAlternate, err := th.createUser("user-alternate", "user-alternate", "alternate helloooo first name", "alternate")
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer th.deleteUser(userAlternate)
|
||||||
|
|
||||||
|
// searching user without specifying team
|
||||||
|
options := createDefaultOptions(true, false, false)
|
||||||
|
users, err := th.Store.User().Search("", "hello", options)
|
||||||
|
require.NoError(t, err)
|
||||||
|
th.assertUsersMatchInAnyOrder(t, []*model.User{userAlternate}, users)
|
||||||
|
|
||||||
|
// adding user to team to search by team
|
||||||
|
err = th.addUserToTeams(userAlternate, []string{th.Team.Id})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = th.addUserToChannels(userAlternate, []string{th.ChannelBasic.Id})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
options = createDefaultOptions(true, false, false)
|
||||||
|
users, err = th.Store.User().Search(th.Team.Id, "hello", options)
|
||||||
|
require.NoError(t, err)
|
||||||
|
th.assertUsersMatchInAnyOrder(t, []*model.User{userAlternate}, users)
|
||||||
|
})
|
||||||
|
t.Run("Should search users by substring in last name name", func(t *testing.T) {
|
||||||
|
userAlternate, err := th.createUser("user-alternate", "user-alternate", "alternate", "alternate helloooo last name")
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer th.deleteUser(userAlternate)
|
||||||
|
|
||||||
|
options := createDefaultOptions(true, false, false)
|
||||||
|
users, err := th.Store.User().Search("", "hello", options)
|
||||||
|
require.NoError(t, err)
|
||||||
|
th.assertUsersMatchInAnyOrder(t, []*model.User{userAlternate}, users)
|
||||||
|
|
||||||
|
// adding user to team to search by team
|
||||||
|
err = th.addUserToTeams(userAlternate, []string{th.Team.Id})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = th.addUserToChannels(userAlternate, []string{th.ChannelBasic.Id})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
options = createDefaultOptions(true, false, false)
|
||||||
|
users, err = th.Store.User().Search(th.Team.Id, "hello", options)
|
||||||
|
require.NoError(t, err)
|
||||||
|
th.assertUsersMatchInAnyOrder(t, []*model.User{userAlternate}, users)
|
||||||
|
})
|
||||||
|
t.Run("Should search users by substring in nickname name", func(t *testing.T) {
|
||||||
|
userAlternate, err := th.createUser("user-alternate", "alternate helloooo nickname", "alternate hello first name", "alternate")
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer th.deleteUser(userAlternate)
|
||||||
|
|
||||||
|
options := createDefaultOptions(true, false, false)
|
||||||
|
users, err := th.Store.User().Search("", "hello", options)
|
||||||
|
require.NoError(t, err)
|
||||||
|
th.assertUsersMatchInAnyOrder(t, []*model.User{userAlternate}, users)
|
||||||
|
|
||||||
|
// adding user to team to search by team
|
||||||
|
err = th.addUserToTeams(userAlternate, []string{th.Team.Id})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = th.addUserToChannels(userAlternate, []string{th.ChannelBasic.Id})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
options = createDefaultOptions(true, false, false)
|
||||||
|
users, err = th.Store.User().Search(th.Team.Id, "hello", options)
|
||||||
|
require.NoError(t, err)
|
||||||
|
th.assertUsersMatchInAnyOrder(t, []*model.User{userAlternate}, users)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func createDefaultOptions(allowFullName, allowEmails, allowInactive bool) *model.UserSearchOptions {
|
func createDefaultOptions(allowFullName, allowEmails, allowInactive bool) *model.UserSearchOptions {
|
||||||
return &model.UserSearchOptions{
|
return &model.UserSearchOptions{
|
||||||
AllowFullNames: allowFullName,
|
AllowFullNames: allowFullName,
|
||||||
|
|||||||
@@ -22,6 +22,25 @@ func newSqlRemoteClusterStore(sqlStore *SqlStore) store.RemoteClusterStore {
|
|||||||
return &sqlRemoteClusterStore{sqlStore}
|
return &sqlRemoteClusterStore{sqlStore}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func remoteClusterFields(prefix string) []string {
|
||||||
|
if prefix != "" && !strings.HasSuffix(prefix, ".") {
|
||||||
|
prefix = prefix + "."
|
||||||
|
}
|
||||||
|
return []string{
|
||||||
|
prefix + "RemoteId",
|
||||||
|
prefix + "RemoteTeamId",
|
||||||
|
prefix + "Name",
|
||||||
|
prefix + "DisplayName",
|
||||||
|
prefix + "SiteURL",
|
||||||
|
prefix + "CreateAt",
|
||||||
|
prefix + "LastPingAt",
|
||||||
|
prefix + "Token",
|
||||||
|
prefix + "RemoteToken",
|
||||||
|
prefix + "Topics",
|
||||||
|
prefix + "CreatorId",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s sqlRemoteClusterStore) Save(remoteCluster *model.RemoteCluster) (*model.RemoteCluster, error) {
|
func (s sqlRemoteClusterStore) Save(remoteCluster *model.RemoteCluster) (*model.RemoteCluster, error) {
|
||||||
remoteCluster.PreSave()
|
remoteCluster.PreSave()
|
||||||
if err := remoteCluster.IsValid(); err != nil {
|
if err := remoteCluster.IsValid(); err != nil {
|
||||||
@@ -89,7 +108,7 @@ func (s sqlRemoteClusterStore) Delete(remoteId string) (bool, error) {
|
|||||||
|
|
||||||
func (s sqlRemoteClusterStore) Get(remoteId string) (*model.RemoteCluster, error) {
|
func (s sqlRemoteClusterStore) Get(remoteId string) (*model.RemoteCluster, error) {
|
||||||
query := s.getQueryBuilder().
|
query := s.getQueryBuilder().
|
||||||
Select("*").
|
Select(remoteClusterFields("")...).
|
||||||
From("RemoteClusters").
|
From("RemoteClusters").
|
||||||
Where(sq.Eq{"RemoteId": remoteId})
|
Where(sq.Eq{"RemoteId": remoteId})
|
||||||
|
|
||||||
@@ -107,7 +126,7 @@ func (s sqlRemoteClusterStore) Get(remoteId string) (*model.RemoteCluster, error
|
|||||||
|
|
||||||
func (s sqlRemoteClusterStore) GetAll(filter model.RemoteClusterQueryFilter) ([]*model.RemoteCluster, error) {
|
func (s sqlRemoteClusterStore) GetAll(filter model.RemoteClusterQueryFilter) ([]*model.RemoteCluster, error) {
|
||||||
query := s.getQueryBuilder().
|
query := s.getQueryBuilder().
|
||||||
Select("rc.*").
|
Select(remoteClusterFields("rc")...).
|
||||||
From("RemoteClusters rc")
|
From("RemoteClusters rc")
|
||||||
|
|
||||||
if filter.InChannel != "" {
|
if filter.InChannel != "" {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ package sqlstore
|
|||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/v6/model"
|
"github.com/mattermost/mattermost-server/v6/model"
|
||||||
"github.com/mattermost/mattermost-server/v6/server/channels/store"
|
"github.com/mattermost/mattermost-server/v6/server/channels/store"
|
||||||
@@ -71,12 +72,32 @@ func (s SqlSharedChannelStore) Save(sc *model.SharedChannel) (sh *model.SharedCh
|
|||||||
return sc, nil
|
return sc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sharedChannelFields(prefix string) []string {
|
||||||
|
if prefix != "" && !strings.HasSuffix(prefix, ".") {
|
||||||
|
prefix = prefix + "."
|
||||||
|
}
|
||||||
|
return []string{
|
||||||
|
prefix + "ChannelId",
|
||||||
|
prefix + "TeamId",
|
||||||
|
prefix + "Home",
|
||||||
|
prefix + "ReadOnly",
|
||||||
|
prefix + "ShareName",
|
||||||
|
prefix + "ShareDisplayName",
|
||||||
|
prefix + "SharePurpose",
|
||||||
|
prefix + "ShareHeader",
|
||||||
|
prefix + "CreatorId",
|
||||||
|
prefix + "CreateAt",
|
||||||
|
prefix + "UpdateAt",
|
||||||
|
prefix + "RemoteId",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get fetches a shared channel by channel_id.
|
// Get fetches a shared channel by channel_id.
|
||||||
func (s SqlSharedChannelStore) Get(channelId string) (*model.SharedChannel, error) {
|
func (s SqlSharedChannelStore) Get(channelId string) (*model.SharedChannel, error) {
|
||||||
var sc model.SharedChannel
|
var sc model.SharedChannel
|
||||||
|
|
||||||
query := s.getQueryBuilder().
|
query := s.getQueryBuilder().
|
||||||
Select("*").
|
Select(sharedChannelFields("")...).
|
||||||
From("SharedChannels").
|
From("SharedChannels").
|
||||||
Where(sq.Eq{"SharedChannels.ChannelId": channelId})
|
Where(sq.Eq{"SharedChannels.ChannelId": channelId})
|
||||||
|
|
||||||
@@ -173,15 +194,15 @@ func (s SqlSharedChannelStore) GetAllCount(opts model.SharedChannelFilterOpts) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s SqlSharedChannelStore) getSharedChannelsQuery(opts model.SharedChannelFilterOpts, forCount bool) sq.SelectBuilder {
|
func (s SqlSharedChannelStore) getSharedChannelsQuery(opts model.SharedChannelFilterOpts, forCount bool) sq.SelectBuilder {
|
||||||
var selectStr string
|
var selectFields []string
|
||||||
if forCount {
|
if forCount {
|
||||||
selectStr = "count(sc.ChannelId)"
|
selectFields = []string{"count(sc.ChannelId)"}
|
||||||
} else {
|
} else {
|
||||||
selectStr = "sc.*"
|
selectFields = sharedChannelFields("sc")
|
||||||
}
|
}
|
||||||
|
|
||||||
query := s.getQueryBuilder().
|
query := s.getQueryBuilder().
|
||||||
Select(selectStr).
|
Select(selectFields...).
|
||||||
From("SharedChannels AS sc")
|
From("SharedChannels AS sc")
|
||||||
|
|
||||||
if opts.MemberId != "" {
|
if opts.MemberId != "" {
|
||||||
@@ -364,12 +385,30 @@ func (s SqlSharedChannelStore) UpdateRemote(remote *model.SharedChannelRemote) (
|
|||||||
return remote, nil
|
return remote, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sharedChannelRemoteFields(prefix string) []string {
|
||||||
|
if prefix != "" && !strings.HasSuffix(prefix, ".") {
|
||||||
|
prefix = prefix + "."
|
||||||
|
}
|
||||||
|
return []string{
|
||||||
|
prefix + "Id",
|
||||||
|
prefix + "ChannelId",
|
||||||
|
prefix + "CreatorId",
|
||||||
|
prefix + "CreateAt",
|
||||||
|
prefix + "UpdateAt",
|
||||||
|
prefix + "IsInviteAccepted",
|
||||||
|
prefix + "IsInviteConfirmed",
|
||||||
|
prefix + "RemoteId",
|
||||||
|
prefix + "LastPostUpdateAt",
|
||||||
|
prefix + "LastPostId",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GetRemote fetches a shared channel remote by id.
|
// GetRemote fetches a shared channel remote by id.
|
||||||
func (s SqlSharedChannelStore) GetRemote(id string) (*model.SharedChannelRemote, error) {
|
func (s SqlSharedChannelStore) GetRemote(id string) (*model.SharedChannelRemote, error) {
|
||||||
var remote model.SharedChannelRemote
|
var remote model.SharedChannelRemote
|
||||||
|
|
||||||
query := s.getQueryBuilder().
|
query := s.getQueryBuilder().
|
||||||
Select("*").
|
Select(sharedChannelRemoteFields("")...).
|
||||||
From("SharedChannelRemotes").
|
From("SharedChannelRemotes").
|
||||||
Where(sq.Eq{"SharedChannelRemotes.Id": id})
|
Where(sq.Eq{"SharedChannelRemotes.Id": id})
|
||||||
|
|
||||||
@@ -392,7 +431,7 @@ func (s SqlSharedChannelStore) GetRemoteByIds(channelId string, remoteId string)
|
|||||||
var remote model.SharedChannelRemote
|
var remote model.SharedChannelRemote
|
||||||
|
|
||||||
query := s.getQueryBuilder().
|
query := s.getQueryBuilder().
|
||||||
Select("*").
|
Select(sharedChannelRemoteFields("")...).
|
||||||
From("SharedChannelRemotes").
|
From("SharedChannelRemotes").
|
||||||
Where(sq.Eq{"SharedChannelRemotes.ChannelId": channelId}).
|
Where(sq.Eq{"SharedChannelRemotes.ChannelId": channelId}).
|
||||||
Where(sq.Eq{"SharedChannelRemotes.RemoteId": remoteId})
|
Where(sq.Eq{"SharedChannelRemotes.RemoteId": remoteId})
|
||||||
@@ -416,7 +455,7 @@ func (s SqlSharedChannelStore) GetRemotes(opts model.SharedChannelRemoteFilterOp
|
|||||||
remotes := []*model.SharedChannelRemote{}
|
remotes := []*model.SharedChannelRemote{}
|
||||||
|
|
||||||
query := s.getQueryBuilder().
|
query := s.getQueryBuilder().
|
||||||
Select("*").
|
Select(sharedChannelRemoteFields("")...).
|
||||||
From("SharedChannelRemotes")
|
From("SharedChannelRemotes")
|
||||||
|
|
||||||
if opts.ChannelId != "" {
|
if opts.ChannelId != "" {
|
||||||
@@ -570,6 +609,20 @@ func (s SqlSharedChannelStore) GetRemotesStatus(channelId string) ([]*model.Shar
|
|||||||
return status, nil
|
return status, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sharedChannelUserFields(prefix string) []string {
|
||||||
|
if prefix != "" && !strings.HasSuffix(prefix, ".") {
|
||||||
|
prefix = prefix + "."
|
||||||
|
}
|
||||||
|
return []string{
|
||||||
|
prefix + "Id",
|
||||||
|
prefix + "UserId",
|
||||||
|
prefix + "ChannelId",
|
||||||
|
prefix + "RemoteId",
|
||||||
|
prefix + "CreateAt",
|
||||||
|
prefix + "LastSyncAt",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SaveUser inserts a new shared channel user record to the SharedChannelUsers table.
|
// SaveUser inserts a new shared channel user record to the SharedChannelUsers table.
|
||||||
func (s SqlSharedChannelStore) SaveUser(scUser *model.SharedChannelUser) (*model.SharedChannelUser, error) {
|
func (s SqlSharedChannelStore) SaveUser(scUser *model.SharedChannelUser) (*model.SharedChannelUser, error) {
|
||||||
scUser.PreSave()
|
scUser.PreSave()
|
||||||
@@ -578,7 +631,7 @@ func (s SqlSharedChannelStore) SaveUser(scUser *model.SharedChannelUser) (*model
|
|||||||
}
|
}
|
||||||
|
|
||||||
query, args, err := s.getQueryBuilder().Insert("SharedChannelUsers").
|
query, args, err := s.getQueryBuilder().Insert("SharedChannelUsers").
|
||||||
Columns("Id", "UserId", "ChannelId", "RemoteId", "CreateAt", "LastSyncAt").
|
Columns(sharedChannelUserFields("")...).
|
||||||
Values(scUser.Id, scUser.UserId, scUser.ChannelId, scUser.RemoteId, scUser.CreateAt, scUser.LastSyncAt).
|
Values(scUser.Id, scUser.UserId, scUser.ChannelId, scUser.RemoteId, scUser.CreateAt, scUser.LastSyncAt).
|
||||||
ToSql()
|
ToSql()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -595,7 +648,7 @@ func (s SqlSharedChannelStore) GetSingleUser(userID string, channelID string, re
|
|||||||
var scu model.SharedChannelUser
|
var scu model.SharedChannelUser
|
||||||
|
|
||||||
squery, args, err := s.getQueryBuilder().
|
squery, args, err := s.getQueryBuilder().
|
||||||
Select("*").
|
Select(sharedChannelUserFields("")...).
|
||||||
From("SharedChannelUsers").
|
From("SharedChannelUsers").
|
||||||
Where(sq.Eq{"SharedChannelUsers.UserId": userID}).
|
Where(sq.Eq{"SharedChannelUsers.UserId": userID}).
|
||||||
Where(sq.Eq{"SharedChannelUsers.RemoteId": remoteID}).
|
Where(sq.Eq{"SharedChannelUsers.RemoteId": remoteID}).
|
||||||
@@ -618,7 +671,7 @@ func (s SqlSharedChannelStore) GetSingleUser(userID string, channelID string, re
|
|||||||
// GetUsersForUser fetches all shared channel user records based on userID.
|
// GetUsersForUser fetches all shared channel user records based on userID.
|
||||||
func (s SqlSharedChannelStore) GetUsersForUser(userID string) ([]*model.SharedChannelUser, error) {
|
func (s SqlSharedChannelStore) GetUsersForUser(userID string) ([]*model.SharedChannelUser, error) {
|
||||||
squery, args, err := s.getQueryBuilder().
|
squery, args, err := s.getQueryBuilder().
|
||||||
Select("*").
|
Select(sharedChannelUserFields("")...).
|
||||||
From("SharedChannelUsers").
|
From("SharedChannelUsers").
|
||||||
Where(sq.Eq{"SharedChannelUsers.UserId": userID}).
|
Where(sq.Eq{"SharedChannelUsers.UserId": userID}).
|
||||||
ToSql()
|
ToSql()
|
||||||
@@ -645,7 +698,9 @@ func (s SqlSharedChannelStore) GetUsersForSync(filter model.GetUsersForSyncFilte
|
|||||||
}
|
}
|
||||||
|
|
||||||
query := s.getQueryBuilder().
|
query := s.getQueryBuilder().
|
||||||
Select("u.*").
|
Select("u.Id", "u.CreateAt", "u.UpdateAt", "u.DeleteAt", "u.Username", "u.Email", "u.EmailVerified",
|
||||||
|
"u.Nickname", "u.FirstName", "u.LastName", "u.Position", "u.Roles", "u.AllowMarketing", "u.Props",
|
||||||
|
"u.NotifyProps", "u.LastPasswordUpdate", "u.LastPictureUpdate", "u.Locale", "u.Timezone", "u.RemoteId").
|
||||||
Distinct().
|
Distinct().
|
||||||
From("Users AS u").
|
From("Users AS u").
|
||||||
Join("SharedChannelUsers AS scu ON u.Id = scu.UserId").
|
Join("SharedChannelUsers AS scu ON u.Id = scu.UserId").
|
||||||
@@ -723,6 +778,19 @@ func (s SqlSharedChannelStore) UpdateUserLastSyncAt(userID string, channelID str
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sharedChannelAttachementFields(prefix string) []string {
|
||||||
|
if prefix != "" && !strings.HasSuffix(prefix, ".") {
|
||||||
|
prefix = prefix + "."
|
||||||
|
}
|
||||||
|
return []string{
|
||||||
|
prefix + "Id",
|
||||||
|
prefix + "FileId",
|
||||||
|
prefix + "RemoteId",
|
||||||
|
prefix + "CreateAt",
|
||||||
|
prefix + "LastSyncAt",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SaveAttachment inserts a new shared channel file attachment record to the SharedChannelFiles table.
|
// SaveAttachment inserts a new shared channel file attachment record to the SharedChannelFiles table.
|
||||||
func (s SqlSharedChannelStore) SaveAttachment(attachment *model.SharedChannelAttachment) (*model.SharedChannelAttachment, error) {
|
func (s SqlSharedChannelStore) SaveAttachment(attachment *model.SharedChannelAttachment) (*model.SharedChannelAttachment, error) {
|
||||||
attachment.PreSave()
|
attachment.PreSave()
|
||||||
@@ -731,7 +799,7 @@ func (s SqlSharedChannelStore) SaveAttachment(attachment *model.SharedChannelAtt
|
|||||||
}
|
}
|
||||||
|
|
||||||
query, args, err := s.getQueryBuilder().Insert("SharedChannelAttachments").
|
query, args, err := s.getQueryBuilder().Insert("SharedChannelAttachments").
|
||||||
Columns("Id", "FileId", "RemoteId", "CreateAt", "LastSyncAt").
|
Columns(sharedChannelAttachementFields("")...).
|
||||||
Values(attachment.Id, attachment.FileId, attachment.RemoteId, attachment.CreateAt, attachment.LastSyncAt).
|
Values(attachment.Id, attachment.FileId, attachment.RemoteId, attachment.CreateAt, attachment.LastSyncAt).
|
||||||
ToSql()
|
ToSql()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -753,7 +821,7 @@ func (s SqlSharedChannelStore) UpsertAttachment(attachment *model.SharedChannelA
|
|||||||
}
|
}
|
||||||
query := s.getQueryBuilder().
|
query := s.getQueryBuilder().
|
||||||
Insert("SharedChannelAttachments").
|
Insert("SharedChannelAttachments").
|
||||||
Columns("Id", "FileId", "RemoteId", "CreateAt", "LastSyncAt").
|
Columns(sharedChannelAttachementFields("")...).
|
||||||
Values(attachment.Id, attachment.FileId, attachment.RemoteId, attachment.CreateAt, attachment.LastSyncAt)
|
Values(attachment.Id, attachment.FileId, attachment.RemoteId, attachment.CreateAt, attachment.LastSyncAt)
|
||||||
|
|
||||||
if s.DriverName() == model.DatabaseDriverMysql {
|
if s.DriverName() == model.DatabaseDriverMysql {
|
||||||
@@ -777,7 +845,7 @@ func (s SqlSharedChannelStore) GetAttachment(fileId string, remoteId string) (*m
|
|||||||
var attachment model.SharedChannelAttachment
|
var attachment model.SharedChannelAttachment
|
||||||
|
|
||||||
squery, args, err := s.getQueryBuilder().
|
squery, args, err := s.getQueryBuilder().
|
||||||
Select("*").
|
Select(sharedChannelAttachementFields("")...).
|
||||||
From("SharedChannelAttachments").
|
From("SharedChannelAttachments").
|
||||||
Where(sq.Eq{"SharedChannelAttachments.FileId": fileId}).
|
Where(sq.Eq{"SharedChannelAttachments.FileId": fileId}).
|
||||||
Where(sq.Eq{"SharedChannelAttachments.RemoteId": remoteId}).
|
Where(sq.Eq{"SharedChannelAttachments.RemoteId": remoteId}).
|
||||||
|
|||||||
@@ -1540,7 +1540,7 @@ func generateSearchQuery(query sq.SelectBuilder, terms []string, fields []string
|
|||||||
} else {
|
} else {
|
||||||
searchFields = append(searchFields, fmt.Sprintf("%s LIKE ? escape '*' ", field))
|
searchFields = append(searchFields, fmt.Sprintf("%s LIKE ? escape '*' ", field))
|
||||||
}
|
}
|
||||||
termArgs = append(termArgs, fmt.Sprintf("%s%%", strings.TrimLeft(term, "@")))
|
termArgs = append(termArgs, fmt.Sprintf("%%%s%%", strings.TrimLeft(term, "@")))
|
||||||
}
|
}
|
||||||
query = query.Where(fmt.Sprintf("(%s)", strings.Join(searchFields, " OR ")), termArgs...)
|
query = query.Where(fmt.Sprintf("(%s)", strings.Join(searchFields, " OR ")), termArgs...)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -223,9 +223,6 @@ func GenerateLimitedClientConfig(c *model.Config, telemetryID string, license *m
|
|||||||
props["BuildHash"] = model.BuildHash
|
props["BuildHash"] = model.BuildHash
|
||||||
props["BuildHashEnterprise"] = model.BuildHashEnterprise
|
props["BuildHashEnterprise"] = model.BuildHashEnterprise
|
||||||
props["BuildEnterpriseReady"] = model.BuildEnterpriseReady
|
props["BuildEnterpriseReady"] = model.BuildEnterpriseReady
|
||||||
props["BuildHashBoards"] = model.BuildHashBoards
|
|
||||||
props["BuildBoards"] = model.BuildBoards
|
|
||||||
props["BuildHashPlaybooks"] = model.BuildHashPlaybooks
|
|
||||||
|
|
||||||
props["EnableBotAccountCreation"] = strconv.FormatBool(*c.ServiceSettings.EnableBotAccountCreation)
|
props["EnableBotAccountCreation"] = strconv.FormatBool(*c.ServiceSettings.EnableBotAccountCreation)
|
||||||
props["EnableFile"] = strconv.FormatBool(*c.LogSettings.EnableFile)
|
props["EnableFile"] = strconv.FormatBool(*c.LogSettings.EnableFile)
|
||||||
|
|||||||
@@ -109,7 +109,6 @@ services:
|
|||||||
- "RUN_SERVER_IN_BACKGROUND=false"
|
- "RUN_SERVER_IN_BACKGROUND=false"
|
||||||
- "MM_CLUSTERSETTINGS_ENABLE=true"
|
- "MM_CLUSTERSETTINGS_ENABLE=true"
|
||||||
- "MM_CLUSTERSETTINGS_CLUSTERNAME=mm_dev_cluster"
|
- "MM_CLUSTERSETTINGS_CLUSTERNAME=mm_dev_cluster"
|
||||||
- "MM_FEATUREFLAGS_BoardsProduct=true"
|
|
||||||
networks:
|
networks:
|
||||||
- mm-test
|
- mm-test
|
||||||
depends_on:
|
depends_on:
|
||||||
@@ -147,7 +146,6 @@ services:
|
|||||||
- "RUN_SERVER_IN_BACKGROUND=false"
|
- "RUN_SERVER_IN_BACKGROUND=false"
|
||||||
- "MM_CLUSTERSETTINGS_ENABLE=true"
|
- "MM_CLUSTERSETTINGS_ENABLE=true"
|
||||||
- "MM_CLUSTERSETTINGS_CLUSTERNAME=mm_dev_cluster"
|
- "MM_CLUSTERSETTINGS_CLUSTERNAME=mm_dev_cluster"
|
||||||
- "MM_FEATUREFLAGS_BoardsProduct=true"
|
|
||||||
networks:
|
networks:
|
||||||
- mm-test
|
- mm-test
|
||||||
depends_on:
|
depends_on:
|
||||||
@@ -185,7 +183,6 @@ services:
|
|||||||
- "RUN_SERVER_IN_BACKGROUND=false"
|
- "RUN_SERVER_IN_BACKGROUND=false"
|
||||||
- "MM_CLUSTERSETTINGS_ENABLE=true"
|
- "MM_CLUSTERSETTINGS_ENABLE=true"
|
||||||
- "MM_CLUSTERSETTINGS_CLUSTERNAME=mm_dev_cluster"
|
- "MM_CLUSTERSETTINGS_CLUSTERNAME=mm_dev_cluster"
|
||||||
- "MM_FEATUREFLAGS_BoardsProduct=true"
|
|
||||||
networks:
|
networks:
|
||||||
- mm-test
|
- mm-test
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|||||||
@@ -2049,6 +2049,10 @@
|
|||||||
"id": "api.license.request-trial.bad-request",
|
"id": "api.license.request-trial.bad-request",
|
||||||
"translation": "The number of users requested is not correct."
|
"translation": "The number of users requested is not correct."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "api.license.request-trial.bad-request.business-email",
|
||||||
|
"translation": "Invalid business email for trial"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "api.license.request-trial.bad-request.terms-not-accepted",
|
"id": "api.license.request-trial.bad-request.terms-not-accepted",
|
||||||
"translation": "You must accept the Mattermost Software Evaluation Agreement and Privacy Policy to request a license."
|
"translation": "You must accept the Mattermost Software Evaluation Agreement and Privacy Policy to request a license."
|
||||||
|
|||||||
@@ -341,7 +341,7 @@ func (pp *playbooksProduct) Start() error {
|
|||||||
logrus.Info("Rudder credentials are set. Enabling analytics.")
|
logrus.Info("Rudder credentials are set. Enabling analytics.")
|
||||||
diagnosticID := pp.serviceAdapter.GetDiagnosticID()
|
diagnosticID := pp.serviceAdapter.GetDiagnosticID()
|
||||||
serverVersion := pp.serviceAdapter.GetServerVersion()
|
serverVersion := pp.serviceAdapter.GetServerVersion()
|
||||||
pp.telemetryClient, err = telemetry.NewRudder(rudderDataplaneURL, rudderWriteKey, diagnosticID, model.BuildHashPlaybooks, serverVersion)
|
pp.telemetryClient, err = telemetry.NewRudder(rudderDataplaneURL, rudderWriteKey, diagnosticID, model.BuildHash, serverVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "failed init telemetry client")
|
return errors.Wrapf(err, "failed init telemetry client")
|
||||||
}
|
}
|
||||||
@@ -569,7 +569,7 @@ func (pp *playbooksProduct) Stop() error {
|
|||||||
func newMetricsInstance() *metrics.Metrics {
|
func newMetricsInstance() *metrics.Metrics {
|
||||||
// Init metrics
|
// Init metrics
|
||||||
instanceInfo := metrics.InstanceInfo{
|
instanceInfo := metrics.InstanceInfo{
|
||||||
Version: model.BuildHashPlaybooks,
|
Version: model.BuildHash,
|
||||||
InstallationID: os.Getenv("MM_CLOUD_INSTALLATION_ID"),
|
InstallationID: os.Getenv("MM_CLOUD_INSTALLATION_ID"),
|
||||||
}
|
}
|
||||||
return metrics.NewMetrics(instanceInfo)
|
return metrics.NewMetrics(instanceInfo)
|
||||||
|
|||||||
@@ -91,16 +91,10 @@ func HandleErrorWithCode(logger logrus.FieldLogger, w http.ResponseWriter, code
|
|||||||
|
|
||||||
// ReturnJSON writes the given pointerToObject as json with the provided httpStatus
|
// ReturnJSON writes the given pointerToObject as json with the provided httpStatus
|
||||||
func ReturnJSON(w http.ResponseWriter, pointerToObject interface{}, httpStatus int) {
|
func ReturnJSON(w http.ResponseWriter, pointerToObject interface{}, httpStatus int) {
|
||||||
jsonBytes, err := json.Marshal(pointerToObject)
|
|
||||||
if err != nil {
|
|
||||||
logrus.WithError(err).Error("Unable to marshal JSON")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.WriteHeader(httpStatus)
|
w.WriteHeader(httpStatus)
|
||||||
|
|
||||||
if _, err = w.Write(jsonBytes); err != nil {
|
if err := json.NewEncoder(w).Encode(pointerToObject); err != nil {
|
||||||
logrus.WithError(err).Warn("Unable to write to http.ResponseWriter")
|
logrus.WithError(err).Warn("Unable to write to http.ResponseWriter")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
save-exact=true
|
save-exact=true
|
||||||
legacy-peer-deps=true
|
legacy-peer-deps=true
|
||||||
|
global-style=true
|
||||||
@@ -89,7 +89,7 @@
|
|||||||
"unused-imports/no-unused-imports": 2,
|
"unused-imports/no-unused-imports": 2,
|
||||||
"no-relative-import-paths/no-relative-import-paths": [
|
"no-relative-import-paths/no-relative-import-paths": [
|
||||||
"error",
|
"error",
|
||||||
{ "allowSameFolder": true, "rootDir": "webapp/src"}
|
{ "allowSameFolder": true, "rootDir": "webapp/boards"}
|
||||||
],
|
],
|
||||||
/* "no-restricted-imports": ["error", {
|
/* "no-restricted-imports": ["error", {
|
||||||
"patterns": ["..*"]
|
"patterns": ["..*"]
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
{
|
{
|
||||||
"AppBar.Tooltip": "Toggle linked boards",
|
"AdminBadge.SystemAdmin": "Admin",
|
||||||
|
"AdminBadge.TeamAdmin": "Team Admin",
|
||||||
|
"AppBar.Tooltip": "Toggle Linked Boards",
|
||||||
"Attachment.Attachment-title": "Attachment",
|
"Attachment.Attachment-title": "Attachment",
|
||||||
"AttachmentBlock.DeleteAction": "delete",
|
"AttachmentBlock.DeleteAction": "delete",
|
||||||
"AttachmentBlock.addElement": "add {type}",
|
"AttachmentBlock.addElement": "add {type}",
|
||||||
@@ -115,7 +117,6 @@
|
|||||||
"CenterPanel.Login": "Login",
|
"CenterPanel.Login": "Login",
|
||||||
"CenterPanel.Share": "Share",
|
"CenterPanel.Share": "Share",
|
||||||
"ChannelIntro.CreateBoard": "Create a board",
|
"ChannelIntro.CreateBoard": "Create a board",
|
||||||
"CloudMessage.cloud-server": "Get your own free cloud server.",
|
|
||||||
"ColorOption.selectColor": "Select {color} Color",
|
"ColorOption.selectColor": "Select {color} Color",
|
||||||
"Comment.delete": "Delete",
|
"Comment.delete": "Delete",
|
||||||
"CommentsList.send": "Send",
|
"CommentsList.send": "Send",
|
||||||
@@ -138,6 +139,7 @@
|
|||||||
"ContentBlock.moveDown": "Move down",
|
"ContentBlock.moveDown": "Move down",
|
||||||
"ContentBlock.moveUp": "Move up",
|
"ContentBlock.moveUp": "Move up",
|
||||||
"ContentBlock.text": "text",
|
"ContentBlock.text": "text",
|
||||||
|
"DateFilter.empty": "Empty",
|
||||||
"DateRange.clear": "Clear",
|
"DateRange.clear": "Clear",
|
||||||
"DateRange.empty": "Empty",
|
"DateRange.empty": "Empty",
|
||||||
"DateRange.endDate": "End date",
|
"DateRange.endDate": "End date",
|
||||||
@@ -156,10 +158,14 @@
|
|||||||
"Filter.ends-with": "ends with",
|
"Filter.ends-with": "ends with",
|
||||||
"Filter.includes": "includes",
|
"Filter.includes": "includes",
|
||||||
"Filter.is": "is",
|
"Filter.is": "is",
|
||||||
|
"Filter.is-after": "is after",
|
||||||
|
"Filter.is-before": "is before",
|
||||||
"Filter.is-empty": "is empty",
|
"Filter.is-empty": "is empty",
|
||||||
"Filter.is-not-empty": "is not empty",
|
"Filter.is-not-empty": "is not empty",
|
||||||
"Filter.is-not-set": "is not set",
|
"Filter.is-not-set": "is not set",
|
||||||
"Filter.is-set": "is set",
|
"Filter.is-set": "is set",
|
||||||
|
"Filter.isafter": "is after",
|
||||||
|
"Filter.isbefore": "is before",
|
||||||
"Filter.not-contains": "doesn't contain",
|
"Filter.not-contains": "doesn't contain",
|
||||||
"Filter.not-ends-with": "doesn't end with",
|
"Filter.not-ends-with": "doesn't end with",
|
||||||
"Filter.not-includes": "doesn't include",
|
"Filter.not-includes": "doesn't include",
|
||||||
@@ -243,16 +249,11 @@
|
|||||||
"ShareBoard.userPermissionsYouText": "(You)",
|
"ShareBoard.userPermissionsYouText": "(You)",
|
||||||
"ShareTemplate.Title": "Share template",
|
"ShareTemplate.Title": "Share template",
|
||||||
"ShareTemplate.searchPlaceholder": "Search for people",
|
"ShareTemplate.searchPlaceholder": "Search for people",
|
||||||
"Sidebar.about": "About Focalboard",
|
|
||||||
"Sidebar.add-board": "+ Add board",
|
|
||||||
"Sidebar.changePassword": "Change password",
|
|
||||||
"Sidebar.delete-board": "Delete board",
|
"Sidebar.delete-board": "Delete board",
|
||||||
"Sidebar.duplicate-board": "Duplicate board",
|
"Sidebar.duplicate-board": "Duplicate board",
|
||||||
"Sidebar.export-archive": "Export archive",
|
"Sidebar.export-archive": "Export archive",
|
||||||
"Sidebar.import": "Import",
|
"Sidebar.import": "Import",
|
||||||
"Sidebar.import-archive": "Import archive",
|
"Sidebar.import-archive": "Import archive",
|
||||||
"Sidebar.invite-users": "Invite users",
|
|
||||||
"Sidebar.logout": "Log out",
|
|
||||||
"Sidebar.new-category.badge": "New",
|
"Sidebar.new-category.badge": "New",
|
||||||
"Sidebar.new-category.drag-boards-cta": "Drag boards here...",
|
"Sidebar.new-category.drag-boards-cta": "Drag boards here...",
|
||||||
"Sidebar.no-boards-in-category": "No boards inside",
|
"Sidebar.no-boards-in-category": "No boards inside",
|
||||||
@@ -306,6 +307,7 @@
|
|||||||
"ValueSelector.valueSelector": "Value selector",
|
"ValueSelector.valueSelector": "Value selector",
|
||||||
"ValueSelectorLabel.openMenu": "Open menu",
|
"ValueSelectorLabel.openMenu": "Open menu",
|
||||||
"VersionMessage.help": "Check out what's new in this version.",
|
"VersionMessage.help": "Check out what's new in this version.",
|
||||||
|
"VersionMessage.learn-more": "Learn more",
|
||||||
"View.AddView": "Add view",
|
"View.AddView": "Add view",
|
||||||
"View.Board": "Board",
|
"View.Board": "Board",
|
||||||
"View.DeleteView": "Delete view",
|
"View.DeleteView": "Delete view",
|
||||||
@@ -360,6 +362,9 @@
|
|||||||
"WelcomePage.StartUsingIt.Text": "Start using it",
|
"WelcomePage.StartUsingIt.Text": "Start using it",
|
||||||
"Workspace.editing-board-template": "You're editing a board template.",
|
"Workspace.editing-board-template": "You're editing a board template.",
|
||||||
"badge.guest": "Guest",
|
"badge.guest": "Guest",
|
||||||
|
"boardPage.confirm-join-button": "Join",
|
||||||
|
"boardPage.confirm-join-text": "You are about to join a private board without explicitly being added by the board admin. Are you sure you wish to join this private board?",
|
||||||
|
"boardPage.confirm-join-title": "Join private board",
|
||||||
"boardSelector.confirm-link-board": "Link board to channel",
|
"boardSelector.confirm-link-board": "Link board to channel",
|
||||||
"boardSelector.confirm-link-board-button": "Yes, link board",
|
"boardSelector.confirm-link-board-button": "Yes, link board",
|
||||||
"boardSelector.confirm-link-board-subtext": "When you link \"{boardName}\" to the channel, all members of the channel (existing and new) will be able to edit it. This excludes members who are guests. You can unlink a board from a channel at any time.",
|
"boardSelector.confirm-link-board-subtext": "When you link \"{boardName}\" to the channel, all members of the channel (existing and new) will be able to edit it. This excludes members who are guests. You can unlink a board from a channel at any time.",
|
||||||
@@ -374,7 +379,6 @@
|
|||||||
"calendar.week": "Week",
|
"calendar.week": "Week",
|
||||||
"centerPanel.undefined": "No {propertyName}",
|
"centerPanel.undefined": "No {propertyName}",
|
||||||
"centerPanel.unknown-user": "Unknown user",
|
"centerPanel.unknown-user": "Unknown user",
|
||||||
"cloudMessage.learn-more": "Learn more",
|
|
||||||
"createImageBlock.failed": "This file couldn't be uploaded as the file size limit has been reached.",
|
"createImageBlock.failed": "This file couldn't be uploaded as the file size limit has been reached.",
|
||||||
"default-properties.badges": "Comments and description",
|
"default-properties.badges": "Comments and description",
|
||||||
"default-properties.title": "Title",
|
"default-properties.title": "Title",
|
||||||
|
|||||||
68
webapp/boards/jest.config.js
Обычный файл
68
webapp/boards/jest.config.js
Обычный файл
@@ -0,0 +1,68 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
/** @type {import('jest').Config} */
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
transform: {
|
||||||
|
"^.+\\.(t|j)sx?$": ["@swc/jest"]
|
||||||
|
},
|
||||||
|
moduleFileExtensions: [
|
||||||
|
"ts",
|
||||||
|
"tsx",
|
||||||
|
"js",
|
||||||
|
"jsx",
|
||||||
|
"json",
|
||||||
|
"node"
|
||||||
|
],
|
||||||
|
extensionsToTreatAsEsm: ['.ts', '.tsx'],
|
||||||
|
transformIgnorePatterns: [
|
||||||
|
"/nanoevents/",
|
||||||
|
"node_modules/(?!react-native|react-router|react-day-picker)"
|
||||||
|
],
|
||||||
|
maxWorkers: "80%",
|
||||||
|
testEnvironment: "jsdom",
|
||||||
|
collectCoverage: true,
|
||||||
|
collectCoverageFrom: [
|
||||||
|
"src/**/*.{ts,tsx,js,jsx}",
|
||||||
|
"!src/test/**"
|
||||||
|
],
|
||||||
|
testPathIgnorePatterns: [
|
||||||
|
"/node_modules/",
|
||||||
|
],
|
||||||
|
clearMocks: true,
|
||||||
|
coverageReporters: [
|
||||||
|
"lcov",
|
||||||
|
"text-summary"
|
||||||
|
],
|
||||||
|
moduleNameMapper: {
|
||||||
|
"^.+\\.(scss|css)$": "<rootDir>/src/test/style_mock.json",
|
||||||
|
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.js",
|
||||||
|
"\\.(scss|css)$": "<rootDir>/__mocks__/styleMock.js",
|
||||||
|
"^bundle-loader\\?lazy\\!(.*)$": "$1",
|
||||||
|
"^src(.*)$": "<rootDir>/src$1",
|
||||||
|
"^i18n(.*)$": "<rootDir>/i18n$1",
|
||||||
|
"^static(.*)$": "<rootDir>/static$1",
|
||||||
|
"^moment(.*)$": "<rootDir>/../node_modules/moment$1",
|
||||||
|
},
|
||||||
|
moduleDirectories: [
|
||||||
|
"src",
|
||||||
|
"node_modules",
|
||||||
|
],
|
||||||
|
reporters: [
|
||||||
|
"default",
|
||||||
|
"jest-junit"
|
||||||
|
],
|
||||||
|
setupFiles: [
|
||||||
|
"jest-canvas-mock"
|
||||||
|
],
|
||||||
|
setupFilesAfterEnv: [
|
||||||
|
"<rootDir>/src/test/setup.tsx"
|
||||||
|
],
|
||||||
|
testTimeout: 60000,
|
||||||
|
testEnvironmentOptions: {
|
||||||
|
url: "http://localhost:8065"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = config;
|
||||||
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
@@ -19,10 +19,11 @@
|
|||||||
"check-types:fix": "npm run check-types -- --noEmit --fix",
|
"check-types:fix": "npm run check-types -- --noEmit --fix",
|
||||||
"check": "npm run check-lint && npm run check-style",
|
"check": "npm run check-lint && npm run check-style",
|
||||||
"fix": "npm run check-lint:fix && npm run check-style:fix",
|
"fix": "npm run check-lint:fix && npm run check-style:fix",
|
||||||
"test": "jest --forceExit --detectOpenHandles --verbose",
|
"test": "cross-env TZ=Etc/UTC jest",
|
||||||
"test:watch": "jest --watch",
|
"test:watch": "cross-env TZ=Etc/UTC jest --watch",
|
||||||
"test:updatesnapshot": "jest --updateSnapshot",
|
"test:updatesnapshot": "cross-env TZ=Etc/UTC jest --updateSnapshot",
|
||||||
"test-ci": "jest --ci --forceExit --detectOpenHandles --maxWorkers=100%",
|
"test:debug": "cross-env TZ=Etc/UTC jest --forceExit --detectOpenHandles --verbose ",
|
||||||
|
"test-ci": "cross-env TZ=Etc/UTC jest --ci --maxWorkers=100%",
|
||||||
"clean": "rm -rf node_modules .eslintcache"
|
"clean": "rm -rf node_modules .eslintcache"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -44,78 +45,26 @@
|
|||||||
"fullcalendar": "^5.10.2",
|
"fullcalendar": "^5.10.2",
|
||||||
"glob-parent": "6.0.2",
|
"glob-parent": "6.0.2",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"marked": "^4.0.12",
|
"marked": "4.0.17",
|
||||||
"mattermost-redux": "5.33.1",
|
|
||||||
"mini-create-react-context": "^0.4.1",
|
"mini-create-react-context": "^0.4.1",
|
||||||
"moment": "^2.29.1",
|
"moment": "^2.29.1",
|
||||||
"nanoevents": "^5.1.13",
|
"nanoevents": "^5.1.13",
|
||||||
"react": "^16.13.0",
|
"react": "17.0.2",
|
||||||
"react-beautiful-dnd": "^13.1.1",
|
"react-beautiful-dnd": "^13.1.1",
|
||||||
"react-day-picker": "^7.4.10",
|
"react-day-picker": "7.4.10",
|
||||||
"react-dnd": "^14.0.2",
|
"react-dnd": "^14.0.2",
|
||||||
"react-dnd-html5-backend": "^14.0.0",
|
"react-dnd-html5-backend": "^14.0.0",
|
||||||
"react-dnd-scrolling": "^1.2.1",
|
"react-dnd-scrolling": "^1.2.1",
|
||||||
"react-dnd-touch-backend": "^14.0.0",
|
"react-dnd-touch-backend": "^14.0.0",
|
||||||
"react-dom": "^16.13.0",
|
"react-dom": "17.0.2",
|
||||||
"react-hot-keys": "^2.7.1",
|
"react-hot-keys": "^2.7.1",
|
||||||
"react-hotkeys-hook": "^3.4.4",
|
"react-hotkeys-hook": "^3.4.4",
|
||||||
"react-intl": "^5.20.0",
|
"react-intl": "^5.20.0",
|
||||||
"react-redux": "^7.2.1",
|
"react-redux": "^7.2.1",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
"react-select": "5.7.0",
|
"react-select": "5.5.9",
|
||||||
"trim-newlines": "^4.0.2"
|
"trim-newlines": "^4.0.2"
|
||||||
},
|
},
|
||||||
"jest": {
|
|
||||||
"transform": {
|
|
||||||
"^.+\\.(js|tsx|ts|tsx)$": "@swc/jest"
|
|
||||||
},
|
|
||||||
"transformIgnorePatterns": [
|
|
||||||
"/nanoevents/",
|
|
||||||
"node_modules/(?!react-native|react-router|mattermost-webapp|react-day-picker)"
|
|
||||||
],
|
|
||||||
"maxWorkers": "50%",
|
|
||||||
"testEnvironment": "jsdom",
|
|
||||||
"collectCoverage": true,
|
|
||||||
"collectCoverageFrom": [
|
|
||||||
"src/**/*.{ts,tsx,js,jsx}",
|
|
||||||
"!src/test/**"
|
|
||||||
],
|
|
||||||
"moduleFileExtensions": ["js", "jsx", "ts", "tsx"],
|
|
||||||
"testPathIgnorePatterns": [
|
|
||||||
"/node_modules/",
|
|
||||||
"/non_npm_dependencies/"
|
|
||||||
],
|
|
||||||
"clearMocks": true,
|
|
||||||
"coverageReporters": [
|
|
||||||
"lcov",
|
|
||||||
"text-summary"
|
|
||||||
],
|
|
||||||
"moduleNameMapper": {
|
|
||||||
"^.+\\.(scss|css)$": "<rootDir>/src/test/style_mock.json",
|
|
||||||
"^.*i18n.*\\.(json)$": "<rootDir>/src/test/i18n_mock.json",
|
|
||||||
"^bundle-loader\\?lazy\\!(.*)$": "$1",
|
|
||||||
"^react$": "<rootDir>/node_modules/react",
|
|
||||||
"^react-redux$": "<rootDir>/node_modules/react-redux",
|
|
||||||
"^react-intl$": "<rootDir>/node_modules/react-intl",
|
|
||||||
"^src(.*)$": "<rootDir>/src$1"
|
|
||||||
},
|
|
||||||
"moduleDirectories": [
|
|
||||||
"src",
|
|
||||||
"node_modules",
|
|
||||||
"non_npm_dependencies"
|
|
||||||
],
|
|
||||||
"reporters": [
|
|
||||||
"default",
|
|
||||||
"jest-junit"
|
|
||||||
],
|
|
||||||
"setupFiles": [
|
|
||||||
"jest-canvas-mock"
|
|
||||||
],
|
|
||||||
"setupFilesAfterEnv": [
|
|
||||||
"<rootDir>/src/test/setup.tsx"
|
|
||||||
],
|
|
||||||
"testURL": "http://localhost:8065"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/cli": "7.17.6",
|
"@babel/cli": "7.17.6",
|
||||||
"@babel/core": "7.17.8",
|
"@babel/core": "7.17.8",
|
||||||
@@ -130,30 +79,32 @@
|
|||||||
"@babel/runtime": "7.17.8",
|
"@babel/runtime": "7.17.8",
|
||||||
"@formatjs/cli": "^4.8.2",
|
"@formatjs/cli": "^4.8.2",
|
||||||
"@formatjs/ts-transformer": "^3.9.2",
|
"@formatjs/ts-transformer": "^3.9.2",
|
||||||
"@swc/jest": "^0.2.24",
|
"@swc/core": "1.3.40",
|
||||||
"@testing-library/dom": "^8.11.4",
|
"@swc/jest": "0.2.24",
|
||||||
"@testing-library/jest-dom": "^5.16.3",
|
"@testing-library/dom": "8.20.0",
|
||||||
"@testing-library/react": "^11.2.5",
|
"@testing-library/jest-dom": "5.16.5",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/react": "12.1.5",
|
||||||
|
"@testing-library/user-event": "14.4.3",
|
||||||
"@types/color": "^3.0.3",
|
"@types/color": "^3.0.3",
|
||||||
"@types/draft-js": "^0.11.9",
|
"@types/draft-js": "^0.11.9",
|
||||||
"@types/emoji-mart": "^3.0.9",
|
"@types/emoji-mart": "^3.0.9",
|
||||||
"@types/enzyme": "3.10.11",
|
"@types/enzyme": "3.10.11",
|
||||||
"@types/jest": "27.4.1",
|
"@types/jest": "29.4.2",
|
||||||
"@types/lodash": "4.14.182",
|
"@types/lodash": "4.14.182",
|
||||||
"@types/marked": "^4.0.3",
|
"@types/marked": "^4.0.3",
|
||||||
"@types/nanoevents": "^1.0.0",
|
"@types/nanoevents": "^1.0.0",
|
||||||
"@types/node": "17.0.23",
|
"@types/node": "16.11.7",
|
||||||
"@types/react": "^17.0.43",
|
"@types/react": "17.0.53",
|
||||||
"@types/react-beautiful-dnd": "^13.1.2",
|
"@types/react-beautiful-dnd": "^13.1.2",
|
||||||
"@types/react-dom": "^17.0.14",
|
"@types/react-day-picker": "5.3.0",
|
||||||
|
"@types/react-dom": "17.0.19",
|
||||||
"@types/react-redux": "^7.1.23",
|
"@types/react-redux": "^7.1.23",
|
||||||
"@types/react-router-dom": "^5.3.3",
|
"@types/react-router-dom": "^5.3.3",
|
||||||
"@types/react-transition-group": "4.4.4",
|
"@types/react-transition-group": "4.4.4",
|
||||||
"@types/redux-mock-store": "1.0.3",
|
"@types/redux-mock-store": "1.0.3",
|
||||||
"@typescript-eslint/eslint-plugin": "5.16.0",
|
"@typescript-eslint/eslint-plugin": "5.16.0",
|
||||||
"@typescript-eslint/parser": "5.16.0",
|
"@typescript-eslint/parser": "5.16.0",
|
||||||
"babel-eslint": "10.1.0",
|
"babel-eslint": "10.1.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"css-loader": "6.7.1",
|
"css-loader": "6.7.1",
|
||||||
"eslint": "^8.11.0",
|
"eslint": "^8.11.0",
|
||||||
@@ -168,7 +119,7 @@
|
|||||||
"eslint-plugin-react": "7.29.4",
|
"eslint-plugin-react": "7.29.4",
|
||||||
"eslint-plugin-react-hooks": "4.3.0",
|
"eslint-plugin-react-hooks": "4.3.0",
|
||||||
"eslint-plugin-unused-imports": "2.0.0",
|
"eslint-plugin-unused-imports": "2.0.0",
|
||||||
"fetch-mock-jest": "^1.5.1",
|
"fetch-mock-jest": "1.5.1",
|
||||||
"identity-obj-proxy": "3.0.0",
|
"identity-obj-proxy": "3.0.0",
|
||||||
"image-webpack-loader": "8.1.0",
|
"image-webpack-loader": "8.1.0",
|
||||||
"imagemin-gifsicle": "^7.0.0",
|
"imagemin-gifsicle": "^7.0.0",
|
||||||
@@ -178,10 +129,12 @@
|
|||||||
"imagemin-svgo": "^10.0.1",
|
"imagemin-svgo": "^10.0.1",
|
||||||
"imagemin-webp": "7.0.0",
|
"imagemin-webp": "7.0.0",
|
||||||
"isomorphic-fetch": "3.0.0",
|
"isomorphic-fetch": "3.0.0",
|
||||||
"jest": "27.5.1",
|
"jest": "29.5.0",
|
||||||
"jest-canvas-mock": "2.3.1",
|
"jest-canvas-mock": "2.4.0",
|
||||||
"jest-junit": "13.0.0",
|
"jest-environment-jsdom": "29.5.0",
|
||||||
"jest-mock": "27.5.1",
|
"jest-fail-on-console": "3.0.2",
|
||||||
|
"jest-junit": "15.0.0",
|
||||||
|
"jest-mock": "29.4.3",
|
||||||
"prettier": "^2.6.1",
|
"prettier": "^2.6.1",
|
||||||
"redux-mock-store": "^1.5.4",
|
"redux-mock-store": "^1.5.4",
|
||||||
"sass": "1.49.9",
|
"sass": "1.49.9",
|
||||||
|
|||||||
@@ -1,46 +1,46 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`block tests correctly generate patches from two blocks should add fields on the new fields added and remove it in the undo 1`] = `
|
exports[`block tests correctly generate patches from two blocks should add fields on the new fields added and remove it in the undo 1`] = `
|
||||||
Array [
|
[
|
||||||
Object {
|
{
|
||||||
"deletedFields": Array [],
|
"deletedFields": [],
|
||||||
"updatedFields": Object {
|
"updatedFields": {
|
||||||
"newField": "new field",
|
"newField": "new field",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Object {
|
{
|
||||||
"deletedFields": Array [
|
"deletedFields": [
|
||||||
"newField",
|
"newField",
|
||||||
],
|
],
|
||||||
"updatedFields": Object {},
|
"updatedFields": {},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`block tests correctly generate patches from two blocks should generate two empty patches for the same block 1`] = `
|
exports[`block tests correctly generate patches from two blocks should generate two empty patches for the same block 1`] = `
|
||||||
Array [
|
[
|
||||||
Object {
|
{
|
||||||
"deletedFields": Array [],
|
"deletedFields": [],
|
||||||
"updatedFields": Object {},
|
"updatedFields": {},
|
||||||
},
|
},
|
||||||
Object {
|
{
|
||||||
"deletedFields": Array [],
|
"deletedFields": [],
|
||||||
"updatedFields": Object {},
|
"updatedFields": {},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`block tests correctly generate patches from two blocks should remove field on the new block added and add it again in the undo 1`] = `
|
exports[`block tests correctly generate patches from two blocks should remove field on the new block added and add it again in the undo 1`] = `
|
||||||
Array [
|
[
|
||||||
Object {
|
{
|
||||||
"deletedFields": Array [
|
"deletedFields": [
|
||||||
"test",
|
"test",
|
||||||
],
|
],
|
||||||
"updatedFields": Object {},
|
"updatedFields": {},
|
||||||
},
|
},
|
||||||
Object {
|
{
|
||||||
"deletedFields": Array [],
|
"deletedFields": [],
|
||||||
"updatedFields": Object {
|
"updatedFields": {
|
||||||
"test": "test",
|
"test": "test",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -48,16 +48,16 @@ Array [
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`block tests correctly generate patches from two blocks should update propertie on the main object and revert it back on the undo 1`] = `
|
exports[`block tests correctly generate patches from two blocks should update propertie on the main object and revert it back on the undo 1`] = `
|
||||||
Array [
|
[
|
||||||
Object {
|
{
|
||||||
"deletedFields": Array [],
|
"deletedFields": [],
|
||||||
"parentId": "new-parent-id",
|
"parentId": "new-parent-id",
|
||||||
"updatedFields": Object {},
|
"updatedFields": {},
|
||||||
},
|
},
|
||||||
Object {
|
{
|
||||||
"deletedFields": Array [],
|
"deletedFields": [],
|
||||||
"parentId": "old-parent-id",
|
"parentId": "old-parent-id",
|
||||||
"updatedFields": Object {},
|
"updatedFields": {},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -1,52 +1,52 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`board tests correctly generate patches for boards and blocks should add fields on update and remove it in the undo 1`] = `
|
exports[`board tests correctly generate patches for boards and blocks should add fields on update and remove it in the undo 1`] = `
|
||||||
Array [
|
[
|
||||||
Object {
|
{
|
||||||
"blockIDs": Array [
|
"blockIDs": [
|
||||||
"test-old-block-id",
|
"test-old-block-id",
|
||||||
],
|
],
|
||||||
"blockPatches": Array [
|
"blockPatches": [
|
||||||
Object {
|
{
|
||||||
"deletedFields": Array [],
|
"deletedFields": [],
|
||||||
"updatedFields": Object {
|
"updatedFields": {
|
||||||
"newField": "new field",
|
"newField": "new field",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"boardIDs": Array [
|
"boardIDs": [
|
||||||
"test-board-id",
|
"test-board-id",
|
||||||
],
|
],
|
||||||
"boardPatches": Array [
|
"boardPatches": [
|
||||||
Object {
|
{
|
||||||
"deletedCardProperties": Array [],
|
"deletedCardProperties": [],
|
||||||
"deletedProperties": Array [],
|
"deletedProperties": [],
|
||||||
"updatedCardProperties": Array [],
|
"updatedCardProperties": [],
|
||||||
"updatedProperties": Object {},
|
"updatedProperties": {},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
Object {
|
{
|
||||||
"blockIDs": Array [
|
"blockIDs": [
|
||||||
"test-old-block-id",
|
"test-old-block-id",
|
||||||
],
|
],
|
||||||
"blockPatches": Array [
|
"blockPatches": [
|
||||||
Object {
|
{
|
||||||
"deletedFields": Array [
|
"deletedFields": [
|
||||||
"newField",
|
"newField",
|
||||||
],
|
],
|
||||||
"updatedFields": Object {},
|
"updatedFields": {},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"boardIDs": Array [
|
"boardIDs": [
|
||||||
"test-board-id",
|
"test-board-id",
|
||||||
],
|
],
|
||||||
"boardPatches": Array [
|
"boardPatches": [
|
||||||
Object {
|
{
|
||||||
"deletedCardProperties": Array [],
|
"deletedCardProperties": [],
|
||||||
"deletedProperties": Array [],
|
"deletedProperties": [],
|
||||||
"updatedCardProperties": Array [],
|
"updatedCardProperties": [],
|
||||||
"updatedProperties": Object {},
|
"updatedProperties": {},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -54,48 +54,48 @@ Array [
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`board tests correctly generate patches for boards and blocks should generate two empty patches for the same board and block 1`] = `
|
exports[`board tests correctly generate patches for boards and blocks should generate two empty patches for the same board and block 1`] = `
|
||||||
Array [
|
[
|
||||||
Object {
|
{
|
||||||
"blockIDs": Array [
|
"blockIDs": [
|
||||||
"test-card-id",
|
"test-card-id",
|
||||||
],
|
],
|
||||||
"blockPatches": Array [
|
"blockPatches": [
|
||||||
Object {
|
{
|
||||||
"deletedFields": Array [],
|
"deletedFields": [],
|
||||||
"updatedFields": Object {},
|
"updatedFields": {},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"boardIDs": Array [
|
"boardIDs": [
|
||||||
"test-board-id",
|
"test-board-id",
|
||||||
],
|
],
|
||||||
"boardPatches": Array [
|
"boardPatches": [
|
||||||
Object {
|
{
|
||||||
"deletedCardProperties": Array [],
|
"deletedCardProperties": [],
|
||||||
"deletedProperties": Array [],
|
"deletedProperties": [],
|
||||||
"updatedCardProperties": Array [],
|
"updatedCardProperties": [],
|
||||||
"updatedProperties": Object {},
|
"updatedProperties": {},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
Object {
|
{
|
||||||
"blockIDs": Array [
|
"blockIDs": [
|
||||||
"test-card-id",
|
"test-card-id",
|
||||||
],
|
],
|
||||||
"blockPatches": Array [
|
"blockPatches": [
|
||||||
Object {
|
{
|
||||||
"deletedFields": Array [],
|
"deletedFields": [],
|
||||||
"updatedFields": Object {},
|
"updatedFields": {},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"boardIDs": Array [
|
"boardIDs": [
|
||||||
"test-board-id",
|
"test-board-id",
|
||||||
],
|
],
|
||||||
"boardPatches": Array [
|
"boardPatches": [
|
||||||
Object {
|
{
|
||||||
"deletedCardProperties": Array [],
|
"deletedCardProperties": [],
|
||||||
"deletedProperties": Array [],
|
"deletedProperties": [],
|
||||||
"updatedCardProperties": Array [],
|
"updatedCardProperties": [],
|
||||||
"updatedProperties": Object {},
|
"updatedProperties": {},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -103,16 +103,16 @@ Array [
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`board tests correctly generate patches from two boards should add card properties on the redo and remove them on the undo 1`] = `
|
exports[`board tests correctly generate patches from two boards should add card properties on the redo and remove them on the undo 1`] = `
|
||||||
Array [
|
[
|
||||||
Object {
|
{
|
||||||
"deletedCardProperties": Array [],
|
"deletedCardProperties": [],
|
||||||
"deletedProperties": Array [],
|
"deletedProperties": [],
|
||||||
"updatedCardProperties": Array [
|
"updatedCardProperties": [
|
||||||
Object {
|
{
|
||||||
"id": "new-property-id",
|
"id": "new-property-id",
|
||||||
"name": "property-name",
|
"name": "property-name",
|
||||||
"options": Array [
|
"options": [
|
||||||
Object {
|
{
|
||||||
"color": "propColorYellow",
|
"color": "propColorYellow",
|
||||||
"id": "opt",
|
"id": "opt",
|
||||||
"value": "val",
|
"value": "val",
|
||||||
@@ -121,30 +121,30 @@ Array [
|
|||||||
"type": "select",
|
"type": "select",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"updatedProperties": Object {},
|
"updatedProperties": {},
|
||||||
},
|
},
|
||||||
Object {
|
{
|
||||||
"deletedCardProperties": Array [
|
"deletedCardProperties": [
|
||||||
"new-property-id",
|
"new-property-id",
|
||||||
],
|
],
|
||||||
"deletedProperties": Array [],
|
"deletedProperties": [],
|
||||||
"updatedCardProperties": Array [],
|
"updatedCardProperties": [],
|
||||||
"updatedProperties": Object {},
|
"updatedProperties": {},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`board tests correctly generate patches from two boards should add card properties on the redo and undo if they exists in both, but differ 1`] = `
|
exports[`board tests correctly generate patches from two boards should add card properties on the redo and undo if they exists in both, but differ 1`] = `
|
||||||
Array [
|
[
|
||||||
Object {
|
{
|
||||||
"deletedCardProperties": Array [],
|
"deletedCardProperties": [],
|
||||||
"deletedProperties": Array [],
|
"deletedProperties": [],
|
||||||
"updatedCardProperties": Array [
|
"updatedCardProperties": [
|
||||||
Object {
|
{
|
||||||
"id": "new-property-id",
|
"id": "new-property-id",
|
||||||
"name": "property-name",
|
"name": "property-name",
|
||||||
"options": Array [
|
"options": [
|
||||||
Object {
|
{
|
||||||
"color": "propColorYellow",
|
"color": "propColorYellow",
|
||||||
"id": "opt",
|
"id": "opt",
|
||||||
"value": "val",
|
"value": "val",
|
||||||
@@ -153,17 +153,17 @@ Array [
|
|||||||
"type": "select",
|
"type": "select",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"updatedProperties": Object {},
|
"updatedProperties": {},
|
||||||
},
|
},
|
||||||
Object {
|
{
|
||||||
"deletedCardProperties": Array [],
|
"deletedCardProperties": [],
|
||||||
"deletedProperties": Array [],
|
"deletedProperties": [],
|
||||||
"updatedCardProperties": Array [
|
"updatedCardProperties": [
|
||||||
Object {
|
{
|
||||||
"id": "new-property-id",
|
"id": "new-property-id",
|
||||||
"name": "a-different-name",
|
"name": "a-different-name",
|
||||||
"options": Array [
|
"options": [
|
||||||
Object {
|
{
|
||||||
"color": "propColorYellow",
|
"color": "propColorYellow",
|
||||||
"id": "opt",
|
"id": "opt",
|
||||||
"value": "val",
|
"value": "val",
|
||||||
@@ -172,22 +172,22 @@ Array [
|
|||||||
"type": "select",
|
"type": "select",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"updatedProperties": Object {},
|
"updatedProperties": {},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`board tests correctly generate patches from two boards should add card properties on the redo and undo if they exists in both, but their options are different 1`] = `
|
exports[`board tests correctly generate patches from two boards should add card properties on the redo and undo if they exists in both, but their options are different 1`] = `
|
||||||
Array [
|
[
|
||||||
Object {
|
{
|
||||||
"deletedCardProperties": Array [],
|
"deletedCardProperties": [],
|
||||||
"deletedProperties": Array [],
|
"deletedProperties": [],
|
||||||
"updatedCardProperties": Array [
|
"updatedCardProperties": [
|
||||||
Object {
|
{
|
||||||
"id": "new-property-id",
|
"id": "new-property-id",
|
||||||
"name": "property-name",
|
"name": "property-name",
|
||||||
"options": Array [
|
"options": [
|
||||||
Object {
|
{
|
||||||
"color": "propColorYellow",
|
"color": "propColorYellow",
|
||||||
"id": "opt",
|
"id": "opt",
|
||||||
"value": "val",
|
"value": "val",
|
||||||
@@ -196,17 +196,17 @@ Array [
|
|||||||
"type": "select",
|
"type": "select",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"updatedProperties": Object {},
|
"updatedProperties": {},
|
||||||
},
|
},
|
||||||
Object {
|
{
|
||||||
"deletedCardProperties": Array [],
|
"deletedCardProperties": [],
|
||||||
"deletedProperties": Array [],
|
"deletedProperties": [],
|
||||||
"updatedCardProperties": Array [
|
"updatedCardProperties": [
|
||||||
Object {
|
{
|
||||||
"id": "new-property-id",
|
"id": "new-property-id",
|
||||||
"name": "property-name",
|
"name": "property-name",
|
||||||
"options": Array [
|
"options": [
|
||||||
Object {
|
{
|
||||||
"color": "propColorBrown",
|
"color": "propColorBrown",
|
||||||
"id": "another-opt",
|
"id": "another-opt",
|
||||||
"value": "val",
|
"value": "val",
|
||||||
@@ -215,45 +215,45 @@ Array [
|
|||||||
"type": "select",
|
"type": "select",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"updatedProperties": Object {},
|
"updatedProperties": {},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`board tests correctly generate patches from two boards should add properties on the update patch and remove them on the undo 1`] = `
|
exports[`board tests correctly generate patches from two boards should add properties on the update patch and remove them on the undo 1`] = `
|
||||||
Array [
|
[
|
||||||
Object {
|
{
|
||||||
"deletedCardProperties": Array [],
|
"deletedCardProperties": [],
|
||||||
"deletedProperties": Array [],
|
"deletedProperties": [],
|
||||||
"updatedCardProperties": Array [],
|
"updatedCardProperties": [],
|
||||||
"updatedProperties": Object {
|
"updatedProperties": {
|
||||||
"prop1": "val1",
|
"prop1": "val1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Object {
|
{
|
||||||
"deletedCardProperties": Array [],
|
"deletedCardProperties": [],
|
||||||
"deletedProperties": Array [
|
"deletedProperties": [
|
||||||
"prop1",
|
"prop1",
|
||||||
],
|
],
|
||||||
"updatedCardProperties": Array [],
|
"updatedCardProperties": [],
|
||||||
"updatedProperties": Object {},
|
"updatedProperties": {},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`board tests correctly generate patches from two boards should generate two empty patches for the same board 1`] = `
|
exports[`board tests correctly generate patches from two boards should generate two empty patches for the same board 1`] = `
|
||||||
Array [
|
[
|
||||||
Object {
|
{
|
||||||
"deletedCardProperties": Array [],
|
"deletedCardProperties": [],
|
||||||
"deletedProperties": Array [],
|
"deletedProperties": [],
|
||||||
"updatedCardProperties": Array [],
|
"updatedCardProperties": [],
|
||||||
"updatedProperties": Object {},
|
"updatedProperties": {},
|
||||||
},
|
},
|
||||||
Object {
|
{
|
||||||
"deletedCardProperties": Array [],
|
"deletedCardProperties": [],
|
||||||
"deletedProperties": Array [],
|
"deletedProperties": [],
|
||||||
"updatedCardProperties": Array [],
|
"updatedCardProperties": [],
|
||||||
"updatedProperties": Object {},
|
"updatedProperties": {},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {Utils} from './utils'
|
|||||||
import {IPropertyTemplate} from './blocks/board'
|
import {IPropertyTemplate} from './blocks/board'
|
||||||
|
|
||||||
jest.mock('./utils')
|
jest.mock('./utils')
|
||||||
const mockedUtils = mocked(Utils, true)
|
const mockedUtils = mocked(Utils)
|
||||||
|
|
||||||
const dayMillis = 24 * 60 * 60 * 1000
|
const dayMillis = 24 * 60 * 60 * 1000
|
||||||
|
|
||||||
|
|||||||
@@ -434,7 +434,7 @@ exports[`components/cardDialog limited card shows hidden view (no toolbar) 1`] =
|
|||||||
<p
|
<p
|
||||||
class="CardDetail__limited-body"
|
class="CardDetail__limited-body"
|
||||||
>
|
>
|
||||||
Upgrade to our Professional or Enterprise plan to view archived cards, have unlimited views per boards, unlimited cards and more.
|
Upgrade to our Professional or Enterprise plan.
|
||||||
<br />
|
<br />
|
||||||
<a
|
<a
|
||||||
class="CardDetail__limited-link"
|
class="CardDetail__limited-link"
|
||||||
|
|||||||
@@ -317,7 +317,7 @@ exports[`components/centerPanel Clicking on the Hidden card count should open a
|
|||||||
</button>
|
</button>
|
||||||
<span
|
<span
|
||||||
class="Label empty "
|
class="Label empty "
|
||||||
title="Items with an empty name property will go here. This column cannot be removed."
|
title="Items with an empty name property will go here. This column can't be removed."
|
||||||
>
|
>
|
||||||
No name
|
No name
|
||||||
</span>
|
</span>
|
||||||
@@ -982,7 +982,7 @@ exports[`components/centerPanel return centerPanel and click on card to show car
|
|||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="Label empty "
|
class="Label empty "
|
||||||
title="Items with an empty name property will go here. This column cannot be removed."
|
title="Items with an empty name property will go here. This column can't be removed."
|
||||||
>
|
>
|
||||||
No name
|
No name
|
||||||
</span>
|
</span>
|
||||||
@@ -1580,7 +1580,7 @@ exports[`components/centerPanel return centerPanel and click on new card to edit
|
|||||||
</button>
|
</button>
|
||||||
<span
|
<span
|
||||||
class="Label empty "
|
class="Label empty "
|
||||||
title="Items with an empty name property will go here. This column cannot be removed."
|
title="Items with an empty name property will go here. This column can't be removed."
|
||||||
>
|
>
|
||||||
No name
|
No name
|
||||||
</span>
|
</span>
|
||||||
@@ -2102,7 +2102,7 @@ exports[`components/centerPanel return centerPanel and press touch 1 with readon
|
|||||||
</button>
|
</button>
|
||||||
<span
|
<span
|
||||||
class="Label empty "
|
class="Label empty "
|
||||||
title="Items with an empty name property will go here. This column cannot be removed."
|
title="Items with an empty name property will go here. This column can't be removed."
|
||||||
>
|
>
|
||||||
No name
|
No name
|
||||||
</span>
|
</span>
|
||||||
@@ -2639,7 +2639,7 @@ exports[`components/centerPanel return centerPanel and press touch ctrl+d for on
|
|||||||
</button>
|
</button>
|
||||||
<span
|
<span
|
||||||
class="Label empty "
|
class="Label empty "
|
||||||
title="Items with an empty name property will go here. This column cannot be removed."
|
title="Items with an empty name property will go here. This column can't be removed."
|
||||||
>
|
>
|
||||||
No name
|
No name
|
||||||
</span>
|
</span>
|
||||||
@@ -3298,7 +3298,7 @@ exports[`components/centerPanel return centerPanel and press touch del for one c
|
|||||||
</button>
|
</button>
|
||||||
<span
|
<span
|
||||||
class="Label empty "
|
class="Label empty "
|
||||||
title="Items with an empty name property will go here. This column cannot be removed."
|
title="Items with an empty name property will go here. This column can't be removed."
|
||||||
>
|
>
|
||||||
No name
|
No name
|
||||||
</span>
|
</span>
|
||||||
@@ -3957,7 +3957,7 @@ exports[`components/centerPanel return centerPanel and press touch esc for one c
|
|||||||
</button>
|
</button>
|
||||||
<span
|
<span
|
||||||
class="Label empty "
|
class="Label empty "
|
||||||
title="Items with an empty name property will go here. This column cannot be removed."
|
title="Items with an empty name property will go here. This column can't be removed."
|
||||||
>
|
>
|
||||||
No name
|
No name
|
||||||
</span>
|
</span>
|
||||||
@@ -4616,7 +4616,7 @@ exports[`components/centerPanel return centerPanel and press touch esc for one c
|
|||||||
</button>
|
</button>
|
||||||
<span
|
<span
|
||||||
class="Label empty "
|
class="Label empty "
|
||||||
title="Items with an empty name property will go here. This column cannot be removed."
|
title="Items with an empty name property will go here. This column can't be removed."
|
||||||
>
|
>
|
||||||
No name
|
No name
|
||||||
</span>
|
</span>
|
||||||
@@ -5275,7 +5275,7 @@ exports[`components/centerPanel return centerPanel and press touch esc for two c
|
|||||||
</button>
|
</button>
|
||||||
<span
|
<span
|
||||||
class="Label empty "
|
class="Label empty "
|
||||||
title="Items with an empty name property will go here. This column cannot be removed."
|
title="Items with an empty name property will go here. This column can't be removed."
|
||||||
>
|
>
|
||||||
No name
|
No name
|
||||||
</span>
|
</span>
|
||||||
@@ -5934,7 +5934,7 @@ exports[`components/centerPanel return centerPanel and press touch esc for two c
|
|||||||
</button>
|
</button>
|
||||||
<span
|
<span
|
||||||
class="Label empty "
|
class="Label empty "
|
||||||
title="Items with an empty name property will go here. This column cannot be removed."
|
title="Items with an empty name property will go here. This column can't be removed."
|
||||||
>
|
>
|
||||||
No name
|
No name
|
||||||
</span>
|
</span>
|
||||||
@@ -6593,7 +6593,7 @@ exports[`components/centerPanel return centerPanel and press touch esc for two c
|
|||||||
</button>
|
</button>
|
||||||
<span
|
<span
|
||||||
class="Label empty "
|
class="Label empty "
|
||||||
title="Items with an empty name property will go here. This column cannot be removed."
|
title="Items with an empty name property will go here. This column can't be removed."
|
||||||
>
|
>
|
||||||
No name
|
No name
|
||||||
</span>
|
</span>
|
||||||
@@ -7252,7 +7252,7 @@ exports[`components/centerPanel return centerPanel and select one card and click
|
|||||||
</button>
|
</button>
|
||||||
<span
|
<span
|
||||||
class="Label empty "
|
class="Label empty "
|
||||||
title="Items with an empty name property will go here. This column cannot be removed."
|
title="Items with an empty name property will go here. This column can't be removed."
|
||||||
>
|
>
|
||||||
No name
|
No name
|
||||||
</span>
|
</span>
|
||||||
@@ -7911,7 +7911,7 @@ exports[`components/centerPanel return centerPanel and select one card and click
|
|||||||
</button>
|
</button>
|
||||||
<span
|
<span
|
||||||
class="Label empty "
|
class="Label empty "
|
||||||
title="Items with an empty name property will go here. This column cannot be removed."
|
title="Items with an empty name property will go here. This column can't be removed."
|
||||||
>
|
>
|
||||||
No name
|
No name
|
||||||
</span>
|
</span>
|
||||||
@@ -8802,7 +8802,7 @@ exports[`components/centerPanel should match snapshot for Kanban 1`] = `
|
|||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="Label empty "
|
class="Label empty "
|
||||||
title="Items with an empty name property will go here. This column cannot be removed."
|
title="Items with an empty name property will go here. This column can't be removed."
|
||||||
>
|
>
|
||||||
No name
|
No name
|
||||||
</span>
|
</span>
|
||||||
@@ -9322,7 +9322,7 @@ exports[`components/centerPanel should match snapshot for Kanban, not shared 1`]
|
|||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="Label empty "
|
class="Label empty "
|
||||||
title="Items with an empty name property will go here. This column cannot be removed."
|
title="Items with an empty name property will go here. This column can't be removed."
|
||||||
>
|
>
|
||||||
No name
|
No name
|
||||||
</span>
|
</span>
|
||||||
@@ -9886,7 +9886,7 @@ exports[`components/centerPanel should match snapshot for Table 1`] = `
|
|||||||
</button>
|
</button>
|
||||||
<span
|
<span
|
||||||
class="Label empty "
|
class="Label empty "
|
||||||
title="Items with an empty name property will go here. This column cannot be removed."
|
title="Items with an empty name property will go here. This column can't be removed."
|
||||||
>
|
>
|
||||||
No name
|
No name
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ exports[`/components/confirmAddUserForNotifications should match snapshot 1`] =
|
|||||||
class="ConfirmAddUserForNotifications"
|
class="ConfirmAddUserForNotifications"
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
fake-username is not a member of the board, and will not receive any notifications about it.
|
fake-username isn't a member of the board, and won't receive any notifications about it.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Do you want to add fake-username to the board?
|
Do you want to add fake-username to the board?
|
||||||
|
|||||||
@@ -1041,7 +1041,7 @@ exports[`properties/person show multiple, display modal 2`] = `
|
|||||||
<span
|
<span
|
||||||
id="aria-context"
|
id="aria-context"
|
||||||
>
|
>
|
||||||
option username-3 focused, 3 of 3. 1 result available. Use Up and Down to choose options, press Enter to select the currently focused option, press Escape to exit the menu, press Tab to select the option and exit the menu.
|
option username-3 focused, 1 of 1. 1 result available. Use Up and Down to choose options, press Enter to select the currently focused option, press Escape to exit the menu, press Tab to select the option and exit the menu.
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -16,36 +16,15 @@ exports[`components/propertyValueElement Generic fields should allow cancel 1`]
|
|||||||
exports[`components/propertyValueElement URL fields should allow cancel 1`] = `
|
exports[`components/propertyValueElement URL fields should allow cancel 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="URLProperty octo-propertyvalue"
|
class="URLProperty"
|
||||||
>
|
>
|
||||||
<a
|
<input
|
||||||
class="link"
|
class="Editable octo-propertyvalue"
|
||||||
href="http://localhost"
|
placeholder="Empty"
|
||||||
rel="noreferrer"
|
style="width: 100%;"
|
||||||
target="_blank"
|
title=""
|
||||||
>
|
value=""
|
||||||
http://localhost
|
/>
|
||||||
</a>
|
|
||||||
<button
|
|
||||||
aria-label="Edit"
|
|
||||||
class="IconButton Button_Edit"
|
|
||||||
title="Edit"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
class="CompassIcon icon-pencil-outline EditIcon"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
aria-label="Copy"
|
|
||||||
class="IconButton Button_Copy"
|
|
||||||
title="Copy"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
class="CompassIcon icon-content-copy content-copy"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ exports[`components/rhsChannelBoardItem render board with menu open 1`] = `
|
|||||||
<div
|
<div
|
||||||
class="menu-subtext text-75 mt-1"
|
class="menu-subtext text-75 mt-1"
|
||||||
>
|
>
|
||||||
You are not an admin of the board
|
You're not an admin of the board
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`/components/viewMenu should match snapshot 1`] = `
|
exports[`/components/viewMenu should match snapshot 1`] = `
|
||||||
Object {
|
{
|
||||||
"asFragment": [Function],
|
"asFragment": [Function],
|
||||||
"baseElement": <body>
|
"baseElement": <body>
|
||||||
<div>
|
<div>
|
||||||
@@ -530,7 +530,7 @@ Object {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`/components/viewMenu should match snapshot, read only 1`] = `
|
exports[`/components/viewMenu should match snapshot, read only 1`] = `
|
||||||
Object {
|
{
|
||||||
"asFragment": [Function],
|
"asFragment": [Function],
|
||||||
"baseElement": <body>
|
"baseElement": <body>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ exports[`src/components/workspace return workspace and showcard 1`] = `
|
|||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<span>
|
<span>
|
||||||
Find Boards
|
Find boards
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -73,6 +73,7 @@ exports[`src/components/workspace return workspace and showcard 1`] = `
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
data-rbd-draggable-context-id="1"
|
data-rbd-draggable-context-id="1"
|
||||||
|
data-rbd-draggable-id="categoryAttributeId1"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="SidebarCategory"
|
class="SidebarCategory"
|
||||||
@@ -80,14 +81,16 @@ exports[`src/components/workspace return workspace and showcard 1`] = `
|
|||||||
<div
|
<div
|
||||||
class="categoryBoardsDroppableArea"
|
class="categoryBoardsDroppableArea"
|
||||||
data-rbd-droppable-context-id="1"
|
data-rbd-droppable-context-id="1"
|
||||||
|
data-rbd-droppable-id="categoryAttributeId1"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="octo-sidebar-item category expanded active"
|
class="octo-sidebar-item category expanded "
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-describedby="rbd-hidden-text-1-hidden-text-5"
|
aria-describedby="rbd-hidden-text-1-hidden-text-5"
|
||||||
class="octo-sidebar-title category-title"
|
class="octo-sidebar-title category-title"
|
||||||
data-rbd-drag-handle-context-id="1"
|
data-rbd-drag-handle-context-id="1"
|
||||||
|
data-rbd-drag-handle-draggable-id="categoryAttributeId1"
|
||||||
draggable="false"
|
draggable="false"
|
||||||
role="button"
|
role="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@@ -463,7 +466,7 @@ exports[`src/components/workspace return workspace and showcard 1`] = `
|
|||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="Label empty "
|
class="Label empty "
|
||||||
title="Items with an empty Property 2 property will go here. This column cannot be removed."
|
title="Items with an empty Property 2 property will go here. This column can't be removed."
|
||||||
>
|
>
|
||||||
No Property 2
|
No Property 2
|
||||||
</span>
|
</span>
|
||||||
@@ -875,7 +878,7 @@ exports[`src/components/workspace return workspace readonly and showcard 1`] = `
|
|||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="Label empty "
|
class="Label empty "
|
||||||
title="Items with an empty Property 2 property will go here. This column cannot be removed."
|
title="Items with an empty Property 2 property will go here. This column can't be removed."
|
||||||
>
|
>
|
||||||
No Property 2
|
No Property 2
|
||||||
</span>
|
</span>
|
||||||
@@ -1097,7 +1100,7 @@ exports[`src/components/workspace should match snapshot 1`] = `
|
|||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<span>
|
<span>
|
||||||
Find Boards
|
Find boards
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1124,6 +1127,7 @@ exports[`src/components/workspace should match snapshot 1`] = `
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
data-rbd-draggable-context-id="0"
|
data-rbd-draggable-context-id="0"
|
||||||
|
data-rbd-draggable-id="categoryAttributeId1"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="SidebarCategory"
|
class="SidebarCategory"
|
||||||
@@ -1131,14 +1135,16 @@ exports[`src/components/workspace should match snapshot 1`] = `
|
|||||||
<div
|
<div
|
||||||
class="categoryBoardsDroppableArea"
|
class="categoryBoardsDroppableArea"
|
||||||
data-rbd-droppable-context-id="0"
|
data-rbd-droppable-context-id="0"
|
||||||
|
data-rbd-droppable-id="categoryAttributeId1"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="octo-sidebar-item category expanded active"
|
class="octo-sidebar-item category expanded "
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-describedby="rbd-hidden-text-0-hidden-text-0"
|
aria-describedby="rbd-hidden-text-0-hidden-text-0"
|
||||||
class="octo-sidebar-title category-title"
|
class="octo-sidebar-title category-title"
|
||||||
data-rbd-drag-handle-context-id="0"
|
data-rbd-drag-handle-context-id="0"
|
||||||
|
data-rbd-drag-handle-draggable-id="categoryAttributeId1"
|
||||||
draggable="false"
|
draggable="false"
|
||||||
role="button"
|
role="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@@ -1514,7 +1520,7 @@ exports[`src/components/workspace should match snapshot 1`] = `
|
|||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="Label empty "
|
class="Label empty "
|
||||||
title="Items with an empty Property 2 property will go here. This column cannot be removed."
|
title="Items with an empty Property 2 property will go here. This column can't be removed."
|
||||||
>
|
>
|
||||||
No Property 2
|
No Property 2
|
||||||
</span>
|
</span>
|
||||||
@@ -1926,7 +1932,7 @@ exports[`src/components/workspace should match snapshot with readonly 1`] = `
|
|||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="Label empty "
|
class="Label empty "
|
||||||
title="Items with an empty Property 2 property will go here. This column cannot be removed."
|
title="Items with an empty Property 2 property will go here. This column can't be removed."
|
||||||
>
|
>
|
||||||
No Property 2
|
No Property 2
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
import React, {ReactElement, ReactNode} from 'react'
|
import React, {ReactElement, ReactNode} from 'react'
|
||||||
import {render, screen, waitFor} from '@testing-library/react'
|
import {render, screen, waitFor} from '@testing-library/react'
|
||||||
|
|
||||||
import '@testing-library/jest-dom'
|
|
||||||
|
|
||||||
import {mocked} from 'jest-mock'
|
import {mocked} from 'jest-mock'
|
||||||
|
|
||||||
@@ -35,7 +34,7 @@ const wrap = (child: ReactNode): ReactElement => (
|
|||||||
)
|
)
|
||||||
|
|
||||||
jest.mock('src/mutator')
|
jest.mock('src/mutator')
|
||||||
const mockedMutator = mocked(mutator, true)
|
const mockedMutator = mocked(mutator)
|
||||||
|
|
||||||
describe('components/addContentMenuItem', () => {
|
describe('components/addContentMenuItem', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -66,7 +65,7 @@ describe('components/addContentMenuItem', () => {
|
|||||||
)
|
)
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
const buttonElement = screen.getByRole('button', {name: 'text'})
|
const buttonElement = screen.getByRole('button', {name: 'text'})
|
||||||
userEvent.click(buttonElement)
|
await userEvent.click(buttonElement)
|
||||||
await waitFor(() => expect(mockedMutator.insertBlock).toBeCalled())
|
await waitFor(() => expect(mockedMutator.insertBlock).toBeCalled())
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -82,7 +81,7 @@ describe('components/addContentMenuItem', () => {
|
|||||||
)
|
)
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
const buttonElement = screen.getByRole('button', {name: 'checkbox'})
|
const buttonElement = screen.getByRole('button', {name: 'checkbox'})
|
||||||
userEvent.click(buttonElement)
|
await userEvent.click(buttonElement)
|
||||||
await waitFor(() => expect(mockedMutator.insertBlock).toBeCalled())
|
await waitFor(() => expect(mockedMutator.insertBlock).toBeCalled())
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -98,11 +97,12 @@ describe('components/addContentMenuItem', () => {
|
|||||||
)
|
)
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
const buttonElement = screen.getByRole('button', {name: 'divider'})
|
const buttonElement = screen.getByRole('button', {name: 'divider'})
|
||||||
userEvent.click(buttonElement)
|
await userEvent.click(buttonElement)
|
||||||
await waitFor(() => expect(mockedMutator.insertBlock).toBeCalled())
|
await waitFor(() => expect(mockedMutator.insertBlock).toBeCalled())
|
||||||
})
|
})
|
||||||
|
|
||||||
test('return an error and empty element from unknown type', () => {
|
test('return an error and empty element from unknown type', () => {
|
||||||
|
jest.spyOn(console, 'error').mockImplementation()
|
||||||
const {container} = render(
|
const {container} = render(
|
||||||
wrap(
|
wrap(
|
||||||
<AddContentMenuItem
|
<AddContentMenuItem
|
||||||
@@ -112,6 +112,8 @@ describe('components/addContentMenuItem', () => {
|
|||||||
/>,
|
/>,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
expect(console.error).toBeCalledWith(expect.stringContaining('addContentMenu, unknown content type: unknown'))
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,16 +2,10 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {
|
import {fireEvent, render, screen} from '@testing-library/react'
|
||||||
fireEvent,
|
|
||||||
render,
|
|
||||||
screen,
|
|
||||||
act
|
|
||||||
} from '@testing-library/react'
|
|
||||||
|
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
|
|
||||||
import '@testing-library/jest-dom'
|
|
||||||
|
|
||||||
import {mocked} from 'jest-mock'
|
import {mocked} from 'jest-mock'
|
||||||
|
|
||||||
@@ -27,7 +21,7 @@ const card = TestBlockFactory.createCard()
|
|||||||
const icon = '👍'
|
const icon = '👍'
|
||||||
|
|
||||||
jest.mock('src/mutator')
|
jest.mock('src/mutator')
|
||||||
const mockedMutator = mocked(mutator, true)
|
const mockedMutator = mocked(mutator)
|
||||||
|
|
||||||
describe('components/blockIconSelector', () => {
|
describe('components/blockIconSelector', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -53,14 +47,14 @@ describe('components/blockIconSelector', () => {
|
|||||||
))
|
))
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
test('return menu on click', () => {
|
test('return menu on click', async () => {
|
||||||
const {container} = render(wrapIntl(
|
const {container} = render(wrapIntl(
|
||||||
<BlockIconSelector
|
<BlockIconSelector
|
||||||
block={card}
|
block={card}
|
||||||
size='l'
|
size='l'
|
||||||
/>,
|
/>,
|
||||||
))
|
))
|
||||||
userEvent.click(screen.getByRole('button', {name: 'menuwrapper'}))
|
await userEvent.click(screen.getByRole('button', {name: 'menuwrapper'}))
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
test('return no menu in readonly', () => {
|
test('return no menu in readonly', () => {
|
||||||
@@ -73,54 +67,50 @@ describe('components/blockIconSelector', () => {
|
|||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('return a new icon after click on random menu', () => {
|
test('return a new icon after click on random menu', async () => {
|
||||||
render(wrapIntl(
|
render(wrapIntl(
|
||||||
<BlockIconSelector
|
<BlockIconSelector
|
||||||
block={card}
|
block={card}
|
||||||
size='l'
|
size='l'
|
||||||
/>,
|
/>,
|
||||||
))
|
))
|
||||||
userEvent.click(screen.getByRole('button', {name: 'menuwrapper'}))
|
await userEvent.click(screen.getByRole('button', {name: 'menuwrapper'}))
|
||||||
const buttonRandom = screen.queryByRole('button', {name: 'Random'})
|
const buttonRandom = screen.queryByRole('button', {name: 'Random'})
|
||||||
expect(buttonRandom).not.toBeNull()
|
expect(buttonRandom).not.toBeNull()
|
||||||
userEvent.click(buttonRandom!)
|
await userEvent.click(buttonRandom!)
|
||||||
expect(mockedMutator.changeBlockIcon).toBeCalledTimes(1)
|
expect(mockedMutator.changeBlockIcon).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('return a new icon after click on EmojiPicker', () => {
|
test('return a new icon after click on EmojiPicker', async () => {
|
||||||
const {container, getByRole, getAllByRole} = render(wrapIntl(
|
const {container, getByRole, getAllByRole} = render(wrapIntl(
|
||||||
<BlockIconSelector
|
<BlockIconSelector
|
||||||
block={card}
|
block={card}
|
||||||
size='l'
|
size='l'
|
||||||
/>,
|
/>,
|
||||||
))
|
))
|
||||||
act(() => {
|
await userEvent.click(getByRole('button', {name: 'menuwrapper'}))
|
||||||
userEvent.click(getByRole('button', {name: 'menuwrapper'}))
|
|
||||||
})
|
|
||||||
const menuPicker = container.querySelector('div#pick')
|
const menuPicker = container.querySelector('div#pick')
|
||||||
expect(menuPicker).not.toBeNull()
|
expect(menuPicker).not.toBeNull()
|
||||||
|
|
||||||
act(() => {
|
fireEvent.mouseEnter(menuPicker!)
|
||||||
fireEvent.mouseEnter(menuPicker!)
|
|
||||||
})
|
|
||||||
|
|
||||||
const allButtonThumbUp = getAllByRole('button', {name: /thumbsup/i})
|
const allButtonThumbUp = getAllByRole('button', {name: /thumbsup/i})
|
||||||
userEvent.click(allButtonThumbUp[0])
|
await userEvent.click(allButtonThumbUp[0])
|
||||||
expect(mockedMutator.changeBlockIcon).toBeCalledTimes(1)
|
expect(mockedMutator.changeBlockIcon).toBeCalledTimes(1)
|
||||||
expect(mockedMutator.changeBlockIcon).toBeCalledWith(card.boardId, card.id, card.fields.icon, '👍')
|
expect(mockedMutator.changeBlockIcon).toBeCalledWith(card.boardId, card.id, card.fields.icon, '👍')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('return no icon after click on remove menu', () => {
|
test('return no icon after click on remove menu', async () => {
|
||||||
const {container, rerender} = render(wrapIntl(
|
const {container, rerender} = render(wrapIntl(
|
||||||
<BlockIconSelector
|
<BlockIconSelector
|
||||||
block={card}
|
block={card}
|
||||||
size='l'
|
size='l'
|
||||||
/>,
|
/>,
|
||||||
))
|
))
|
||||||
userEvent.click(screen.getByRole('button', {name: 'menuwrapper'}))
|
await userEvent.click(screen.getByRole('button', {name: 'menuwrapper'}))
|
||||||
const buttonRemove = screen.queryByRole('button', {name: 'Remove icon'})
|
const buttonRemove = screen.queryByRole('button', {name: 'Remove icon'})
|
||||||
expect(buttonRemove).not.toBeNull()
|
expect(buttonRemove).not.toBeNull()
|
||||||
userEvent.click(buttonRemove!)
|
await userEvent.click(buttonRemove!)
|
||||||
expect(mockedMutator.changeBlockIcon).toBeCalledTimes(1)
|
expect(mockedMutator.changeBlockIcon).toBeCalledTimes(1)
|
||||||
expect(mockedMutator.changeBlockIcon).toBeCalledWith(card.boardId, card.id, card.fields.icon, '', 'remove icon')
|
expect(mockedMutator.changeBlockIcon).toBeCalledWith(card.boardId, card.id, card.fields.icon, '', 'remove icon')
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,18 @@ exports[`components/blocksEditor/blocksEditor should match snapshot on empty 1`]
|
|||||||
<span
|
<span
|
||||||
class="css-1f43avz-a11yText-A11yText"
|
class="css-1f43avz-a11yText-A11yText"
|
||||||
id="react-select-2-live-region"
|
id="react-select-2-live-region"
|
||||||
/>
|
>
|
||||||
|
<span
|
||||||
|
id="aria-selection"
|
||||||
|
>
|
||||||
|
option , selected.
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
id="aria-context"
|
||||||
|
>
|
||||||
|
Select is focused ,type to refine list, press Down to open the menu,
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
<span
|
<span
|
||||||
aria-atomic="false"
|
aria-atomic="false"
|
||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
@@ -22,7 +33,7 @@ exports[`components/blocksEditor/blocksEditor should match snapshot on empty 1`]
|
|||||||
class="css-1f43avz-a11yText-A11yText"
|
class="css-1f43avz-a11yText-A11yText"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class=" css-4bb158-control"
|
class=" css-1x912dq-control"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class=" css-30zlo3-ValueContainer"
|
class=" css-30zlo3-ValueContainer"
|
||||||
@@ -394,7 +405,18 @@ exports[`components/blocksEditor/blocksEditor should match snapshot with blocks
|
|||||||
<span
|
<span
|
||||||
class="css-1f43avz-a11yText-A11yText"
|
class="css-1f43avz-a11yText-A11yText"
|
||||||
id="react-select-3-live-region"
|
id="react-select-3-live-region"
|
||||||
/>
|
>
|
||||||
|
<span
|
||||||
|
id="aria-selection"
|
||||||
|
>
|
||||||
|
option , selected.
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
id="aria-context"
|
||||||
|
>
|
||||||
|
Select is focused ,type to refine list, press Down to open the menu,
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
<span
|
<span
|
||||||
aria-atomic="false"
|
aria-atomic="false"
|
||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
@@ -402,7 +424,7 @@ exports[`components/blocksEditor/blocksEditor should match snapshot with blocks
|
|||||||
class="css-1f43avz-a11yText-A11yText"
|
class="css-1f43avz-a11yText-A11yText"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class=" css-4bb158-control"
|
class=" css-1x912dq-control"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class=" css-30zlo3-ValueContainer"
|
class=" css-30zlo3-ValueContainer"
|
||||||
|
|||||||
@@ -76,7 +76,18 @@ exports[`components/blocksEditor/editor should match snapshot on empty 1`] = `
|
|||||||
<span
|
<span
|
||||||
class="css-1f43avz-a11yText-A11yText"
|
class="css-1f43avz-a11yText-A11yText"
|
||||||
id="react-select-2-live-region"
|
id="react-select-2-live-region"
|
||||||
/>
|
>
|
||||||
|
<span
|
||||||
|
id="aria-selection"
|
||||||
|
>
|
||||||
|
option , selected.
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
id="aria-context"
|
||||||
|
>
|
||||||
|
Select is focused ,type to refine list, press Down to open the menu,
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
<span
|
<span
|
||||||
aria-atomic="false"
|
aria-atomic="false"
|
||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
@@ -84,7 +95,7 @@ exports[`components/blocksEditor/editor should match snapshot on empty 1`] = `
|
|||||||
class="css-1f43avz-a11yText-A11yText"
|
class="css-1f43avz-a11yText-A11yText"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class=" css-4bb158-control"
|
class=" css-1x912dq-control"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class=" css-30zlo3-ValueContainer"
|
class=" css-30zlo3-ValueContainer"
|
||||||
|
|||||||
@@ -8,7 +8,18 @@ exports[`components/blocksEditor/rootInput should match Display snapshot 1`] = `
|
|||||||
<span
|
<span
|
||||||
class="css-1f43avz-a11yText-A11yText"
|
class="css-1f43avz-a11yText-A11yText"
|
||||||
id="react-select-2-live-region"
|
id="react-select-2-live-region"
|
||||||
/>
|
>
|
||||||
|
<span
|
||||||
|
id="aria-selection"
|
||||||
|
>
|
||||||
|
option , selected.
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
id="aria-context"
|
||||||
|
>
|
||||||
|
Select is focused ,type to refine list, press Down to open the menu,
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
<span
|
<span
|
||||||
aria-atomic="false"
|
aria-atomic="false"
|
||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
@@ -16,7 +27,7 @@ exports[`components/blocksEditor/rootInput should match Display snapshot 1`] = `
|
|||||||
class="css-1f43avz-a11yText-A11yText"
|
class="css-1f43avz-a11yText-A11yText"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class=" css-4bb158-control"
|
class=" css-1x912dq-control"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class=" css-30zlo3-ValueContainer"
|
class=" css-30zlo3-ValueContainer"
|
||||||
@@ -60,7 +71,18 @@ exports[`components/blocksEditor/rootInput should match Input snapshot 1`] = `
|
|||||||
<span
|
<span
|
||||||
class="css-1f43avz-a11yText-A11yText"
|
class="css-1f43avz-a11yText-A11yText"
|
||||||
id="react-select-3-live-region"
|
id="react-select-3-live-region"
|
||||||
/>
|
>
|
||||||
|
<span
|
||||||
|
id="aria-selection"
|
||||||
|
>
|
||||||
|
option , selected.
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
id="aria-context"
|
||||||
|
>
|
||||||
|
Select is focused ,type to refine list, press Down to open the menu,
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
<span
|
<span
|
||||||
aria-atomic="false"
|
aria-atomic="false"
|
||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
@@ -68,7 +90,7 @@ exports[`components/blocksEditor/rootInput should match Input snapshot 1`] = `
|
|||||||
class="css-1f43avz-a11yText-A11yText"
|
class="css-1f43avz-a11yText-A11yText"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class=" css-4bb158-control"
|
class=" css-1x912dq-control"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class=" css-30zlo3-ValueContainer"
|
class=" css-30zlo3-ValueContainer"
|
||||||
@@ -118,9 +140,18 @@ exports[`components/blocksEditor/rootInput should match Input snapshot with menu
|
|||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
aria-relevant="additions text"
|
aria-relevant="additions text"
|
||||||
class="css-1f43avz-a11yText-A11yText"
|
class="css-1f43avz-a11yText-A11yText"
|
||||||
/>
|
>
|
||||||
|
<span
|
||||||
|
id="aria-selection"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
id="aria-context"
|
||||||
|
>
|
||||||
|
option /title Creates a new Title block. focused, 1 of 11. 11 results available. Use Up and Down to choose options, press Enter to select the currently focused option, press Escape to exit the menu, press Tab to select the option and exit the menu.
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
<div
|
<div
|
||||||
class=" css-4bb158-control"
|
class=" css-1x912dq-control"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class=" css-30zlo3-ValueContainer"
|
class=" css-30zlo3-ValueContainer"
|
||||||
@@ -161,7 +192,7 @@ exports[`components/blocksEditor/rootInput should match Input snapshot with menu
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class=" css-1aj7brc"
|
class=" css-u0i6pk-MenuPortal"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class=" css-1rsmi4x-menu"
|
class=" css-1rsmi4x-menu"
|
||||||
|
|||||||
@@ -10,7 +10,12 @@ import {
|
|||||||
act
|
act
|
||||||
} from '@testing-library/react'
|
} from '@testing-library/react'
|
||||||
|
|
||||||
import {mockDOM, wrapDNDIntl, mockStateStore} from 'src/testUtils'
|
import {
|
||||||
|
mockDOM,
|
||||||
|
wrapDNDIntl,
|
||||||
|
mockStateStore,
|
||||||
|
setup
|
||||||
|
} from 'src/testUtils'
|
||||||
import {TestBlockFactory} from 'src/test/testBlockFactory'
|
import {TestBlockFactory} from 'src/test/testBlockFactory'
|
||||||
|
|
||||||
import BlockContent from './blockContent'
|
import BlockContent from './blockContent'
|
||||||
@@ -141,26 +146,28 @@ describe('components/blocksEditor/blockContent', () => {
|
|||||||
|
|
||||||
test('should call onSave on hit enter in the input', async () => {
|
test('should call onSave on hit enter in the input', async () => {
|
||||||
const onSave = jest.fn()
|
const onSave = jest.fn()
|
||||||
await act(async () => {
|
|
||||||
render(wrapDNDIntl(
|
const {user} = setup(wrapDNDIntl(
|
||||||
<ReduxProvider store={store}>
|
<ReduxProvider store={store}>
|
||||||
<BlockContent
|
<BlockContent
|
||||||
boardId='fake-board-id'
|
boardId='fake-board-id'
|
||||||
block={block}
|
block={block}
|
||||||
contentOrder={[block.id]}
|
contentOrder={[block.id]}
|
||||||
editing={block}
|
editing={block}
|
||||||
setEditing={jest.fn()}
|
setEditing={jest.fn()}
|
||||||
setAfterBlock={jest.fn()}
|
setAfterBlock={jest.fn()}
|
||||||
onSave={onSave}
|
onSave={onSave}
|
||||||
onMove={jest.fn()}
|
onMove={jest.fn()}
|
||||||
/>
|
/>
|
||||||
</ReduxProvider>,
|
</ReduxProvider>,
|
||||||
))
|
))
|
||||||
})
|
|
||||||
const input = screen.getByDisplayValue('Title')
|
const input = screen.getByDisplayValue('Title')
|
||||||
expect(onSave).not.toBeCalled()
|
expect(onSave).not.toBeCalled()
|
||||||
fireEvent.change(input, {target: {value: 'test'}})
|
await act(async () => {
|
||||||
fireEvent.keyDown(input, {key: 'Enter'})
|
await user.clear(input)
|
||||||
|
await user.type(input, 'test')
|
||||||
|
await user.keyboard('{Enter}')
|
||||||
|
})
|
||||||
|
|
||||||
expect(onSave).toBeCalledWith(expect.objectContaining({value: 'test'}))
|
expect(onSave).toBeCalledWith(expect.objectContaining({value: 'test'}))
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ jest.mock('src/octoClient')
|
|||||||
|
|
||||||
describe('components/blocksEditor/blocks/attachment', () => {
|
describe('components/blocksEditor/blocks/attachment', () => {
|
||||||
test('should match Display snapshot', async () => {
|
test('should match Display snapshot', async () => {
|
||||||
const mockedOcto = mocked(octoClient, true)
|
const mockedOcto = mocked(octoClient)
|
||||||
mockedOcto.getFileAsDataUrl.mockResolvedValue({url: 'test.jpg'})
|
mockedOcto.getFileAsDataUrl.mockResolvedValue({url: 'test.jpg'})
|
||||||
const Component = AttachmentBlock.Display
|
const Component = AttachmentBlock.Display
|
||||||
const {container} = render(
|
const {container} = render(
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ jest.mock('src/octoClient')
|
|||||||
|
|
||||||
describe('components/blocksEditor/blocks/image', () => {
|
describe('components/blocksEditor/blocks/image', () => {
|
||||||
test('should match Display snapshot', async () => {
|
test('should match Display snapshot', async () => {
|
||||||
const mockedOcto = mocked(octoClient, true)
|
const mockedOcto = mocked(octoClient)
|
||||||
mockedOcto.getFileAsDataUrl.mockResolvedValue({url: 'test.jpg'})
|
mockedOcto.getFileAsDataUrl.mockResolvedValue({url: 'test.jpg'})
|
||||||
const Component = ImageBlock.Display
|
const Component = ImageBlock.Display
|
||||||
const {container} = render(
|
const {container} = render(
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ jest.mock('src/octoClient')
|
|||||||
|
|
||||||
describe('components/blocksEditor/blocks/video', () => {
|
describe('components/blocksEditor/blocks/video', () => {
|
||||||
test('should match Display snapshot', async () => {
|
test('should match Display snapshot', async () => {
|
||||||
const mockedOcto = mocked(octoClient, true)
|
const mockedOcto = mocked(octoClient)
|
||||||
mockedOcto.getFileAsDataUrl.mockResolvedValue({url: 'test.jpg'})
|
mockedOcto.getFileAsDataUrl.mockResolvedValue({url: 'test.jpg'})
|
||||||
const Component = VideoBlock.Display
|
const Component = VideoBlock.Display
|
||||||
const {container} = render(
|
const {container} = render(
|
||||||
|
|||||||
@@ -10,9 +10,15 @@ import {
|
|||||||
act
|
act
|
||||||
} from '@testing-library/react'
|
} from '@testing-library/react'
|
||||||
|
|
||||||
import {mockDOM, wrapDNDIntl, mockStateStore} from 'src/testUtils'
|
import {
|
||||||
|
mockDOM,
|
||||||
|
wrapDNDIntl,
|
||||||
|
mockStateStore,
|
||||||
|
setup
|
||||||
|
} from 'src/testUtils'
|
||||||
import {TestBlockFactory} from 'src/test/testBlockFactory'
|
import {TestBlockFactory} from 'src/test/testBlockFactory'
|
||||||
|
|
||||||
|
|
||||||
import {BlockData} from './blocks/types'
|
import {BlockData} from './blocks/types'
|
||||||
import BlocksEditor from './blocksEditor'
|
import BlocksEditor from './blocksEditor'
|
||||||
|
|
||||||
@@ -95,28 +101,25 @@ describe('components/blocksEditor/blocksEditor', () => {
|
|||||||
|
|
||||||
test('should call onBlockCreate after introduce text and hit enter', async () => {
|
test('should call onBlockCreate after introduce text and hit enter', async () => {
|
||||||
const onBlockCreated = jest.fn()
|
const onBlockCreated = jest.fn()
|
||||||
|
const {user} = setup(wrapDNDIntl(
|
||||||
|
<ReduxProvider store={store}>
|
||||||
|
<BlocksEditor
|
||||||
|
boardId='test-board'
|
||||||
|
onBlockCreated={onBlockCreated}
|
||||||
|
onBlockModified={jest.fn()}
|
||||||
|
onBlockMoved={jest.fn()}
|
||||||
|
blocks={[]}
|
||||||
|
/>
|
||||||
|
</ReduxProvider>,
|
||||||
|
))
|
||||||
|
expect(onBlockCreated).not.toBeCalled()
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
render(wrapDNDIntl(
|
await user.type(screen.getByRole('combobox'), '/title')
|
||||||
<ReduxProvider store={store}>
|
await user.keyboard('{Enter}')
|
||||||
<BlocksEditor
|
await user.type(screen.getByRole('textbox'), 'test')
|
||||||
boardId='test-board'
|
await user.keyboard('{Enter}')
|
||||||
onBlockCreated={onBlockCreated}
|
|
||||||
onBlockModified={jest.fn()}
|
|
||||||
onBlockMoved={jest.fn()}
|
|
||||||
blocks={[]}
|
|
||||||
/>
|
|
||||||
</ReduxProvider>,
|
|
||||||
))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
let input = screen.getByDisplayValue('')
|
|
||||||
expect(onBlockCreated).not.toBeCalled()
|
|
||||||
fireEvent.change(input, {target: {value: '/title'}})
|
|
||||||
fireEvent.keyDown(input, {key: 'Enter'})
|
|
||||||
|
|
||||||
input = screen.getByDisplayValue('')
|
|
||||||
fireEvent.change(input, {target: {value: 'test'}})
|
|
||||||
fireEvent.keyDown(input, {key: 'Enter'})
|
|
||||||
|
|
||||||
expect(onBlockCreated).toBeCalledWith(expect.objectContaining({value: 'test'}))
|
expect(onBlockCreated).toBeCalledWith(expect.objectContaining({value: 'test'}))
|
||||||
})
|
})
|
||||||
@@ -138,7 +141,7 @@ describe('components/blocksEditor/blocksEditor', () => {
|
|||||||
const input = screen.getByTestId('checkbox-check')
|
const input = screen.getByTestId('checkbox-check')
|
||||||
expect(onBlockModified).not.toBeCalled()
|
expect(onBlockModified).not.toBeCalled()
|
||||||
fireEvent.click(input)
|
fireEvent.click(input)
|
||||||
expect(onBlockModified).toBeCalledWith(expect.objectContaining({value: {checked: false, value: 'Checkbox'}}))
|
|
||||||
})
|
})
|
||||||
|
expect(onBlockModified).toBeCalledWith(expect.objectContaining({value: {checked: false, value: 'Checkbox'}}))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,14 +3,14 @@
|
|||||||
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {Provider as ReduxProvider} from 'react-redux'
|
import {Provider as ReduxProvider} from 'react-redux'
|
||||||
import {
|
import {render, screen, act} from '@testing-library/react'
|
||||||
render,
|
|
||||||
screen,
|
|
||||||
fireEvent,
|
|
||||||
act
|
|
||||||
} from '@testing-library/react'
|
|
||||||
|
|
||||||
import {mockDOM, wrapDNDIntl, mockStateStore} from 'src/testUtils'
|
import {
|
||||||
|
mockDOM,
|
||||||
|
wrapDNDIntl,
|
||||||
|
mockStateStore,
|
||||||
|
setup
|
||||||
|
} from 'src/testUtils'
|
||||||
import {TestBlockFactory} from 'src/test/testBlockFactory'
|
import {TestBlockFactory} from 'src/test/testBlockFactory'
|
||||||
|
|
||||||
import Editor from './editor'
|
import Editor from './editor'
|
||||||
@@ -82,25 +82,20 @@ describe('components/blocksEditor/editor', () => {
|
|||||||
|
|
||||||
test('should call onSave after introduce text and hit enter', async () => {
|
test('should call onSave after introduce text and hit enter', async () => {
|
||||||
const onSave = jest.fn()
|
const onSave = jest.fn()
|
||||||
|
const {user} = setup(wrapDNDIntl(
|
||||||
|
<ReduxProvider store={store}>
|
||||||
|
<Editor
|
||||||
|
boardId='fake-board-id'
|
||||||
|
onSave={onSave}
|
||||||
|
/>
|
||||||
|
</ReduxProvider>,
|
||||||
|
))
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
render(wrapDNDIntl(
|
await user.type(screen.getByRole('combobox'), '/title')
|
||||||
<ReduxProvider store={store}>
|
await user.keyboard('{Enter}')
|
||||||
<Editor
|
await user.type(screen.getByRole('textbox'), 'test')
|
||||||
boardId='fake-board-id'
|
await user.keyboard('{Enter}')
|
||||||
onSave={onSave}
|
|
||||||
/>
|
|
||||||
</ReduxProvider>,
|
|
||||||
))
|
|
||||||
})
|
})
|
||||||
let input = screen.getByDisplayValue('')
|
|
||||||
expect(onSave).not.toBeCalled()
|
|
||||||
fireEvent.change(input, {target: {value: '/title'}})
|
|
||||||
fireEvent.keyDown(input, {key: 'Enter'})
|
|
||||||
expect(onSave).not.toBeCalled()
|
|
||||||
|
|
||||||
input = screen.getByDisplayValue('')
|
|
||||||
fireEvent.change(input, {target: {value: 'test'}})
|
|
||||||
fireEvent.keyDown(input, {key: 'Enter'})
|
|
||||||
|
|
||||||
expect(onSave).toBeCalledWith(expect.objectContaining({value: 'test'}))
|
expect(onSave).toBeCalledWith(expect.objectContaining({value: 'test'}))
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
import React, {useState} from 'react'
|
import React, {useState} from 'react'
|
||||||
import Select from 'react-select'
|
import Select, {StylesConfig} from 'react-select'
|
||||||
import {CSSObject} from '@emotion/serialize'
|
|
||||||
|
|
||||||
import {getSelectBaseStyle} from 'src/theme'
|
import {getSelectBaseStyle} from 'src/theme'
|
||||||
|
|
||||||
@@ -16,11 +15,11 @@ type Props = {
|
|||||||
value: string
|
value: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseStyles = getSelectBaseStyle()
|
const baseStyles = getSelectBaseStyle<ContentType>()
|
||||||
|
|
||||||
const styles = {
|
const styles: StylesConfig<ContentType> = {
|
||||||
...baseStyles,
|
...baseStyles,
|
||||||
control: (provided: CSSObject): CSSObject => ({
|
control: (provided) => ({
|
||||||
...provided,
|
...provided,
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
@@ -29,12 +28,12 @@ const styles = {
|
|||||||
color: 'rgb(var(--center-channel-color-rgb))',
|
color: 'rgb(var(--center-channel-color-rgb))',
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
}),
|
}),
|
||||||
input: (provided: CSSObject): CSSObject => ({
|
input: (provided) => ({
|
||||||
...provided,
|
...provided,
|
||||||
background: 'rgb(var(--center-channel-bg-rgb))',
|
background: 'rgb(var(--center-channel-bg-rgb))',
|
||||||
color: 'rgb(var(--center-channel-color-rgb))',
|
color: 'rgb(var(--center-channel-color-rgb))',
|
||||||
}),
|
}),
|
||||||
menu: (provided: CSSObject): CSSObject => ({
|
menu: (provided) => ({
|
||||||
...provided,
|
...provided,
|
||||||
minWidth: '100%',
|
minWidth: '100%',
|
||||||
width: 'max-content',
|
width: 'max-content',
|
||||||
@@ -42,7 +41,7 @@ const styles = {
|
|||||||
left: '0',
|
left: '0',
|
||||||
marginBottom: '0',
|
marginBottom: '0',
|
||||||
}),
|
}),
|
||||||
menuPortal: (provided: CSSObject): CSSObject => ({
|
menuPortal: (provided) => ({
|
||||||
...provided,
|
...provided,
|
||||||
zIndex: 999,
|
zIndex: 999,
|
||||||
}),
|
}),
|
||||||
@@ -52,7 +51,7 @@ export default function RootInput(props: Props) {
|
|||||||
const [showMenu, setShowMenu] = useState(false)
|
const [showMenu, setShowMenu] = useState(false)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select<ContentType>
|
||||||
styles={styles}
|
styles={styles}
|
||||||
components={{DropdownIndicator: () => null, IndicatorSeparator: () => null}}
|
components={{DropdownIndicator: () => null, IndicatorSeparator: () => null}}
|
||||||
className='RootInput'
|
className='RootInput'
|
||||||
@@ -62,8 +61,8 @@ export default function RootInput(props: Props) {
|
|||||||
menuPortalTarget={document.getElementById('focalboard-root-portal')}
|
menuPortalTarget={document.getElementById('focalboard-root-portal')}
|
||||||
menuPosition={'fixed'}
|
menuPosition={'fixed'}
|
||||||
options={registry.list()}
|
options={registry.list()}
|
||||||
getOptionValue={(ct: ContentType) => ct.slashCommand}
|
getOptionValue={(ct) => ct.slashCommand}
|
||||||
getOptionLabel={(ct: ContentType) => ct.slashCommand + ' Creates a new ' + ct.displayName + ' block.'}
|
getOptionLabel={(ct) => ct.slashCommand + ' Creates a new ' + ct.displayName + ' block.'}
|
||||||
filterOption={(option: any, inputValue: string): boolean => {
|
filterOption={(option: any, inputValue: string): boolean => {
|
||||||
return inputValue.startsWith(option.value) || option.value.startsWith(inputValue)
|
return inputValue.startsWith(option.value) || option.value.startsWith(inputValue)
|
||||||
}}
|
}}
|
||||||
@@ -90,7 +89,7 @@ export default function RootInput(props: Props) {
|
|||||||
props.onChange('')
|
props.onChange('')
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onFocus={(e: React.FocusEvent) => {
|
onFocus={(e) => {
|
||||||
const target = e.currentTarget
|
const target = e.currentTarget
|
||||||
target.scrollIntoView({block: 'center'})
|
target.scrollIntoView({block: 'center'})
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import {wrapIntl} from 'src/testUtils'
|
|||||||
import BoardSelector from './boardSelector'
|
import BoardSelector from './boardSelector'
|
||||||
|
|
||||||
jest.mock('src/octoClient')
|
jest.mock('src/octoClient')
|
||||||
const mockedOctoClient = mocked(octoClient, true)
|
const mockedOctoClient = mocked(octoClient)
|
||||||
|
|
||||||
const wait = (ms: number) => {
|
const wait = (ms: number) => {
|
||||||
return new Promise<void>((resolve) => {
|
return new Promise<void>((resolve) => {
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ exports[`components/boardTemplateSelector/boardTemplateSelector a focalboard Plu
|
|||||||
class="CompassIcon icon-kanban"
|
class="CompassIcon icon-kanban"
|
||||||
/>
|
/>
|
||||||
<span>
|
<span>
|
||||||
Create empty board
|
Create an empty board
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -284,7 +284,7 @@ exports[`components/boardTemplateSelector/boardTemplateSelector a focalboard Plu
|
|||||||
class="CompassIcon icon-kanban"
|
class="CompassIcon icon-kanban"
|
||||||
/>
|
/>
|
||||||
<span>
|
<span>
|
||||||
Create empty board
|
Create an empty board
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -436,7 +436,7 @@ exports[`components/boardTemplateSelector/boardTemplateSelector a focalboard Plu
|
|||||||
class="CompassIcon icon-kanban"
|
class="CompassIcon icon-kanban"
|
||||||
/>
|
/>
|
||||||
<span>
|
<span>
|
||||||
Create empty board
|
Create an empty board
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ exports[`components/boardTemplateSelector/boardTemplateSelectorPreview should ma
|
|||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="Label empty "
|
class="Label empty "
|
||||||
title="Items with an empty name property will go here. This column cannot be removed."
|
title="Items with an empty name property will go here. This column can't be removed."
|
||||||
>
|
>
|
||||||
No name
|
No name
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
import {
|
import {
|
||||||
|
act,
|
||||||
render,
|
render,
|
||||||
screen,
|
screen,
|
||||||
act,
|
|
||||||
waitFor,
|
waitFor,
|
||||||
within
|
within
|
||||||
} from '@testing-library/react'
|
} from '@testing-library/react'
|
||||||
@@ -50,11 +50,11 @@ jest.mock('src/mutator')
|
|||||||
jest.mock('src/utils')
|
jest.mock('src/utils')
|
||||||
|
|
||||||
jest.mock('src/telemetry/telemetryClient')
|
jest.mock('src/telemetry/telemetryClient')
|
||||||
const mockedTelemetry = mocked(TelemetryClient, true)
|
const mockedTelemetry = mocked(TelemetryClient)
|
||||||
|
|
||||||
describe('components/boardTemplateSelector/boardTemplateSelector', () => {
|
describe('components/boardTemplateSelector/boardTemplateSelector', () => {
|
||||||
const mockedMutator = mocked(Mutator, true)
|
const mockedMutator = mocked(Mutator)
|
||||||
const mockedOctoClient = mocked(client, true)
|
const mockedOctoClient = mocked(client)
|
||||||
const team1: Team = {
|
const team1: Team = {
|
||||||
id: 'team-1',
|
id: 'team-1',
|
||||||
title: 'Team 1',
|
title: 'Team 1',
|
||||||
@@ -194,7 +194,7 @@ describe('components/boardTemplateSelector/boardTemplateSelector', () => {
|
|||||||
), {wrapper: MemoryRouter})
|
), {wrapper: MemoryRouter})
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
test('return BoardTemplateSelector and click close call the onClose callback', () => {
|
test('return BoardTemplateSelector and click close call the onClose callback', async () => {
|
||||||
const onClose = jest.fn()
|
const onClose = jest.fn()
|
||||||
const {container} = render(wrapDNDIntl(
|
const {container} = render(wrapDNDIntl(
|
||||||
<ReduxProvider store={store}>
|
<ReduxProvider store={store}>
|
||||||
@@ -204,10 +204,10 @@ describe('components/boardTemplateSelector/boardTemplateSelector', () => {
|
|||||||
), {wrapper: MemoryRouter})
|
), {wrapper: MemoryRouter})
|
||||||
const divCloseButton = container.querySelector('div.toolbar .CloseIcon')
|
const divCloseButton = container.querySelector('div.toolbar .CloseIcon')
|
||||||
expect(divCloseButton).not.toBeNull()
|
expect(divCloseButton).not.toBeNull()
|
||||||
userEvent.click(divCloseButton!)
|
await userEvent.click(divCloseButton!)
|
||||||
expect(onClose).toBeCalledTimes(1)
|
expect(onClose).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
test('return BoardTemplateSelector and click new template', () => {
|
test('return BoardTemplateSelector and click new template', async () => {
|
||||||
render(wrapDNDIntl(
|
render(wrapDNDIntl(
|
||||||
<ReduxProvider store={store}>
|
<ReduxProvider store={store}>
|
||||||
<BoardTemplateSelector onClose={jest.fn()}/>
|
<BoardTemplateSelector onClose={jest.fn()}/>
|
||||||
@@ -216,7 +216,7 @@ describe('components/boardTemplateSelector/boardTemplateSelector', () => {
|
|||||||
), {wrapper: MemoryRouter})
|
), {wrapper: MemoryRouter})
|
||||||
const divNewTemplate = screen.getByText('Create new template').parentElement
|
const divNewTemplate = screen.getByText('Create new template').parentElement
|
||||||
expect(divNewTemplate).not.toBeNull()
|
expect(divNewTemplate).not.toBeNull()
|
||||||
userEvent.click(divNewTemplate!)
|
await userEvent.click(divNewTemplate!)
|
||||||
expect(mockedMutator.addEmptyBoardTemplate).toBeCalledTimes(1)
|
expect(mockedMutator.addEmptyBoardTemplate).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
test('return BoardTemplateSelector and click empty board', async () => {
|
test('return BoardTemplateSelector and click empty board', async () => {
|
||||||
@@ -230,9 +230,9 @@ describe('components/boardTemplateSelector/boardTemplateSelector', () => {
|
|||||||
,
|
,
|
||||||
), {wrapper: MemoryRouter})
|
), {wrapper: MemoryRouter})
|
||||||
|
|
||||||
const divEmptyboard = screen.getByText('Create empty board').parentElement
|
const divEmptyboard = screen.getByText('Create an empty board').parentElement
|
||||||
expect(divEmptyboard).not.toBeNull()
|
expect(divEmptyboard).not.toBeNull()
|
||||||
userEvent.click(divEmptyboard!)
|
await userEvent.click(divEmptyboard!)
|
||||||
expect(mockedMutator.addEmptyBoard).toBeCalledTimes(1)
|
expect(mockedMutator.addEmptyBoard).toBeCalledTimes(1)
|
||||||
await waitFor(() => expect(mockedMutator.updateBoard).toBeCalledWith(newBoard, newBoard, 'linked channel'))
|
await waitFor(() => expect(mockedMutator.updateBoard).toBeCalledWith(newBoard, newBoard, 'linked channel'))
|
||||||
})
|
})
|
||||||
@@ -247,17 +247,13 @@ describe('components/boardTemplateSelector/boardTemplateSelector', () => {
|
|||||||
), {wrapper: MemoryRouter, container: document.body.appendChild(root)})
|
), {wrapper: MemoryRouter, container: document.body.appendChild(root)})
|
||||||
const deleteIcon = screen.getByText(template1Title).parentElement?.querySelector('.DeleteIcon')
|
const deleteIcon = screen.getByText(template1Title).parentElement?.querySelector('.DeleteIcon')
|
||||||
expect(deleteIcon).not.toBeNull()
|
expect(deleteIcon).not.toBeNull()
|
||||||
act(() => {
|
await act(() => userEvent.click(deleteIcon!))
|
||||||
userEvent.click(deleteIcon!)
|
|
||||||
})
|
|
||||||
|
|
||||||
const {getByText} = within(root)
|
const {getByText} = within(root)
|
||||||
const deleteConfirm = getByText('Delete')
|
const deleteConfirm = getByText('Delete')
|
||||||
expect(deleteConfirm).not.toBeNull()
|
expect(deleteConfirm).not.toBeNull()
|
||||||
|
|
||||||
await act(async () => {
|
await act(() => userEvent.click(deleteConfirm!))
|
||||||
await userEvent.click(deleteConfirm!)
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(mockedMutator.deleteBoard).toBeCalledTimes(1)
|
expect(mockedMutator.deleteBoard).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
@@ -273,7 +269,7 @@ describe('components/boardTemplateSelector/boardTemplateSelector', () => {
|
|||||||
))
|
))
|
||||||
const editIcon = screen.getByText(template1Title).parentElement?.querySelector('.EditIcon')
|
const editIcon = screen.getByText(template1Title).parentElement?.querySelector('.EditIcon')
|
||||||
expect(editIcon).not.toBeNull()
|
expect(editIcon).not.toBeNull()
|
||||||
userEvent.click(editIcon!)
|
await userEvent.click(editIcon!)
|
||||||
})
|
})
|
||||||
test('return BoardTemplateSelector and click to add board from template', async () => {
|
test('return BoardTemplateSelector and click to add board from template', async () => {
|
||||||
const newBoard = createBoard({id: 'new-board'} as Board)
|
const newBoard = createBoard({id: 'new-board'} as Board)
|
||||||
@@ -288,15 +284,12 @@ describe('components/boardTemplateSelector/boardTemplateSelector', () => {
|
|||||||
const divBoardToSelect = screen.getByText(template1Title).parentElement
|
const divBoardToSelect = screen.getByText(template1Title).parentElement
|
||||||
expect(divBoardToSelect).not.toBeNull()
|
expect(divBoardToSelect).not.toBeNull()
|
||||||
|
|
||||||
act(() => {
|
await userEvent.click(divBoardToSelect!)
|
||||||
userEvent.click(divBoardToSelect!)
|
|
||||||
})
|
|
||||||
|
|
||||||
const useTemplateButton = screen.getByText('Use this template').parentElement
|
const useTemplateButton = screen.getByText('Use this template').parentElement
|
||||||
expect(useTemplateButton).not.toBeNull()
|
expect(useTemplateButton).not.toBeNull()
|
||||||
act(() => {
|
|
||||||
userEvent.click(useTemplateButton!)
|
await userEvent.click(useTemplateButton!)
|
||||||
})
|
|
||||||
|
|
||||||
await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledTimes(1))
|
await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledTimes(1))
|
||||||
await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledWith(team1.id, expect.anything(), expect.anything(), expect.anything(), '1', team1.id))
|
await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledWith(team1.id, expect.anything(), expect.anything(), expect.anything(), '1', team1.id))
|
||||||
@@ -319,15 +312,13 @@ describe('components/boardTemplateSelector/boardTemplateSelector', () => {
|
|||||||
const divBoardToSelect = screen.getByText(template1Title).parentElement
|
const divBoardToSelect = screen.getByText(template1Title).parentElement
|
||||||
expect(divBoardToSelect).not.toBeNull()
|
expect(divBoardToSelect).not.toBeNull()
|
||||||
|
|
||||||
act(() => {
|
await userEvent.click(divBoardToSelect!)
|
||||||
userEvent.click(divBoardToSelect!)
|
|
||||||
})
|
|
||||||
|
|
||||||
const useTemplateButton = screen.getByText('Use this template').parentElement
|
const useTemplateButton = screen.getByText('Use this template').parentElement
|
||||||
expect(useTemplateButton).not.toBeNull()
|
expect(useTemplateButton).not.toBeNull()
|
||||||
act(() => {
|
|
||||||
userEvent.click(useTemplateButton!)
|
await userEvent.click(useTemplateButton!)
|
||||||
})
|
|
||||||
|
|
||||||
await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledTimes(1))
|
await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledTimes(1))
|
||||||
await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledWith(team1.id, expect.anything(), expect.anything(), expect.anything(), '1', team1.id))
|
await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledWith(team1.id, expect.anything(), expect.anything(), expect.anything(), '1', team1.id))
|
||||||
@@ -347,15 +338,13 @@ describe('components/boardTemplateSelector/boardTemplateSelector', () => {
|
|||||||
const divBoardToSelect = screen.getByText(globalTemplateTitle).parentElement
|
const divBoardToSelect = screen.getByText(globalTemplateTitle).parentElement
|
||||||
expect(divBoardToSelect).not.toBeNull()
|
expect(divBoardToSelect).not.toBeNull()
|
||||||
|
|
||||||
act(() => {
|
await userEvent.click(divBoardToSelect!)
|
||||||
userEvent.click(divBoardToSelect!)
|
|
||||||
})
|
|
||||||
|
|
||||||
const useTemplateButton = screen.getByText('Use this template').parentElement
|
const useTemplateButton = screen.getByText('Use this template').parentElement
|
||||||
expect(useTemplateButton).not.toBeNull()
|
expect(useTemplateButton).not.toBeNull()
|
||||||
act(() => {
|
|
||||||
userEvent.click(useTemplateButton!)
|
await userEvent.click(useTemplateButton!)
|
||||||
})
|
|
||||||
await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledTimes(1))
|
await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledTimes(1))
|
||||||
await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledWith(team1.id, expect.anything(), expect.anything(), expect.anything(), 'global-1', team1.id))
|
await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledWith(team1.id, expect.anything(), expect.anything(), expect.anything(), 'global-1', team1.id))
|
||||||
await waitFor(() => expect(mockedTelemetry.trackEvent).toBeCalledWith('boards', 'createBoardViaTemplate', {boardTemplateId: 'template_id_global'}))
|
await waitFor(() => expect(mockedTelemetry.trackEvent).toBeCalledWith('boards', 'createBoardViaTemplate', {boardTemplateId: 'template_id_global'}))
|
||||||
@@ -374,16 +363,13 @@ describe('components/boardTemplateSelector/boardTemplateSelector', () => {
|
|||||||
const divBoardToSelect = screen.getByText('Welcome to Boards!').parentElement
|
const divBoardToSelect = screen.getByText('Welcome to Boards!').parentElement
|
||||||
expect(divBoardToSelect).not.toBeNull()
|
expect(divBoardToSelect).not.toBeNull()
|
||||||
|
|
||||||
act(() => {
|
await userEvent.click(divBoardToSelect!)
|
||||||
userEvent.click(divBoardToSelect!)
|
|
||||||
})
|
|
||||||
|
|
||||||
const useTemplateButton = screen.getByText('Use this template').parentElement
|
const useTemplateButton = screen.getByText('Use this template').parentElement
|
||||||
expect(useTemplateButton).not.toBeNull()
|
expect(useTemplateButton).not.toBeNull()
|
||||||
act(() => {
|
|
||||||
userEvent.click(useTemplateButton!)
|
await userEvent.click(useTemplateButton!)
|
||||||
})
|
|
||||||
|
|
||||||
await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledTimes(1))
|
await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledTimes(1))
|
||||||
await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledWith(team1.id, expect.anything(), expect.anything(), expect.anything(), '2', team1.id))
|
await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledWith(team1.id, expect.anything(), expect.anything(), expect.anything(), '2', team1.id))
|
||||||
await waitFor(() => expect(mockedTelemetry.trackEvent).toBeCalledWith('boards', 'createBoardViaTemplate', {boardTemplateId: 'template_id_2'}))
|
await waitFor(() => expect(mockedTelemetry.trackEvent).toBeCalledWith('boards', 'createBoardViaTemplate', {boardTemplateId: 'template_id_2'}))
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ describe('components/boardTemplateSelector/boardTemplateSelectorItem', () => {
|
|||||||
</ReduxProvider>
|
</ReduxProvider>
|
||||||
,
|
,
|
||||||
))
|
))
|
||||||
userEvent.click(container.querySelector('.BoardTemplateSelectorItem')!)
|
await userEvent.click(container.querySelector('.BoardTemplateSelectorItem')!)
|
||||||
expect(onSelect).toBeCalledTimes(1)
|
expect(onSelect).toBeCalledTimes(1)
|
||||||
expect(onSelect).toBeCalledWith(template)
|
expect(onSelect).toBeCalledWith(template)
|
||||||
expect(onDelete).not.toBeCalled()
|
expect(onDelete).not.toBeCalled()
|
||||||
@@ -222,7 +222,7 @@ describe('components/boardTemplateSelector/boardTemplateSelectorItem', () => {
|
|||||||
</ReduxProvider>
|
</ReduxProvider>
|
||||||
,
|
,
|
||||||
))
|
))
|
||||||
userEvent.click(container.querySelector('.BoardTemplateSelectorItem .EditIcon')!)
|
await userEvent.click(container.querySelector('.BoardTemplateSelectorItem .EditIcon')!)
|
||||||
expect(onEdit).toBeCalledTimes(1)
|
expect(onEdit).toBeCalledTimes(1)
|
||||||
expect(onEdit).toBeCalledWith(template.id)
|
expect(onEdit).toBeCalledWith(template.id)
|
||||||
expect(onSelect).not.toBeCalled()
|
expect(onSelect).not.toBeCalled()
|
||||||
@@ -248,16 +248,12 @@ describe('components/boardTemplateSelector/boardTemplateSelectorItem', () => {
|
|||||||
</ReduxProvider>
|
</ReduxProvider>
|
||||||
,
|
,
|
||||||
), {container: document.body.appendChild(root)})
|
), {container: document.body.appendChild(root)})
|
||||||
act(() => {
|
await act(() => userEvent.click(root.querySelector('.BoardTemplateSelectorItem .DeleteIcon')!))
|
||||||
userEvent.click(root.querySelector('.BoardTemplateSelectorItem .DeleteIcon')!)
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(root).toMatchSnapshot()
|
expect(root).toMatchSnapshot()
|
||||||
|
|
||||||
const {getByText} = within(root)
|
const {getByText} = within(root)
|
||||||
act(() => {
|
await act(() => userEvent.click(getByText('Delete')!))
|
||||||
userEvent.click(getByText('Delete')!)
|
|
||||||
})
|
|
||||||
|
|
||||||
await waitFor(async () => expect(onDelete).toBeCalledTimes(1))
|
await waitFor(async () => expect(onDelete).toBeCalledTimes(1))
|
||||||
await waitFor(async () => expect(onDelete).toBeCalledWith(template))
|
await waitFor(async () => expect(onDelete).toBeCalledWith(template))
|
||||||
|
|||||||
@@ -96,3 +96,9 @@ exports[`components/boardsUnfurl/BoardsUnfurl renders when limited 1`] = `
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`components/boardsUnfurl/BoardsUnfurl test invalid card, invalid block 1`] = `<div />`;
|
||||||
|
|
||||||
|
exports[`components/boardsUnfurl/BoardsUnfurl test invalid card, valid block 1`] = `<div />`;
|
||||||
|
|
||||||
|
exports[`components/boardsUnfurl/BoardsUnfurl test no card 1`] = `<div />`;
|
||||||
|
|||||||
@@ -16,12 +16,14 @@ import {createBoard} from 'src/blocks/board'
|
|||||||
import octoClient from 'src/octoClient'
|
import octoClient from 'src/octoClient'
|
||||||
import {wrapIntl} from 'src/testUtils'
|
import {wrapIntl} from 'src/testUtils'
|
||||||
|
|
||||||
|
import {createBoardView} from 'src/blocks/boardView'
|
||||||
|
|
||||||
import BoardsUnfurl from './boardsUnfurl'
|
import BoardsUnfurl from './boardsUnfurl'
|
||||||
|
|
||||||
jest.mock('src/octoClient')
|
jest.mock('src/octoClient')
|
||||||
jest.mock('src/utils')
|
jest.mock('src/utils')
|
||||||
const mockedOctoClient = mocked(octoClient, true)
|
const mockedOctoClient = mocked(octoClient)
|
||||||
const mockedUtils = mocked(Utils, true)
|
const mockedUtils = mocked(Utils)
|
||||||
mockedUtils.createGuid = jest.requireActual('src/utils').Utils.createGuid
|
mockedUtils.createGuid = jest.requireActual('src/utils').Utils.createGuid
|
||||||
mockedUtils.blockTypeToIDType = jest.requireActual('src/utils').Utils.blockTypeToIDType
|
mockedUtils.blockTypeToIDType = jest.requireActual('src/utils').Utils.blockTypeToIDType
|
||||||
mockedUtils.displayDateTime = jest.requireActual('src/utils').Utils.displayDateTime
|
mockedUtils.displayDateTime = jest.requireActual('src/utils').Utils.displayDateTime
|
||||||
@@ -114,5 +116,118 @@ describe('components/boardsUnfurl/BoardsUnfurl', () => {
|
|||||||
|
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('test no card', async () => {
|
||||||
|
const mockStore = configureStore([])
|
||||||
|
const store = mockStore({
|
||||||
|
language: {
|
||||||
|
value: 'en',
|
||||||
|
},
|
||||||
|
teams: {
|
||||||
|
allTeams: [team],
|
||||||
|
current: team,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const board = {...createBoard(), title: 'test board'}
|
||||||
|
// mockedOctoClient.getBoard.mockResolvedValueOnce(board)
|
||||||
|
|
||||||
|
const component = (
|
||||||
|
<ReduxProvider store={store}>
|
||||||
|
{wrapIntl(
|
||||||
|
<BoardsUnfurl
|
||||||
|
embed={{data: JSON.stringify({workspaceID: 'foo', cardID: '', boardID: board.id, readToken: 'abc', originalPath: '/test'})}}
|
||||||
|
/>,
|
||||||
|
)}
|
||||||
|
</ReduxProvider>
|
||||||
|
)
|
||||||
|
|
||||||
|
let container: Element | DocumentFragment | null = null
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
const result = render(component)
|
||||||
|
container = result.container
|
||||||
|
})
|
||||||
|
expect(container).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('test invalid card, valid block', async () => {
|
||||||
|
const mockStore = configureStore([])
|
||||||
|
const store = mockStore({
|
||||||
|
language: {
|
||||||
|
value: 'en',
|
||||||
|
},
|
||||||
|
teams: {
|
||||||
|
allTeams: [team],
|
||||||
|
current: team,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const cards = [{...createBoardView(), title: 'test view', updateAt: 12345}]
|
||||||
|
const board = {...createBoard(), title: 'test board'}
|
||||||
|
|
||||||
|
mockedOctoClient.getBlocksWithBlockID.mockResolvedValueOnce(cards)
|
||||||
|
mockedOctoClient.getBoard.mockResolvedValueOnce(board)
|
||||||
|
|
||||||
|
const component = (
|
||||||
|
<ReduxProvider store={store}>
|
||||||
|
{wrapIntl(
|
||||||
|
<BoardsUnfurl
|
||||||
|
embed={{data: JSON.stringify({workspaceID: 'foo', cardID: cards[0].id, boardID: board.id, readToken: 'abc', originalPath: '/test'})}}
|
||||||
|
/>,
|
||||||
|
)}
|
||||||
|
</ReduxProvider>
|
||||||
|
)
|
||||||
|
|
||||||
|
let container: Element | DocumentFragment | null = null
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
const result = render(component)
|
||||||
|
container = result.container
|
||||||
|
})
|
||||||
|
expect(mockedOctoClient.getBoard).toBeCalledWith(board.id)
|
||||||
|
expect(mockedOctoClient.getBlocksWithBlockID).toBeCalledWith(cards[0].id, board.id, 'abc')
|
||||||
|
|
||||||
|
expect(container).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('test invalid card, invalid block', async () => {
|
||||||
|
const mockStore = configureStore([])
|
||||||
|
const store = mockStore({
|
||||||
|
language: {
|
||||||
|
value: 'en',
|
||||||
|
},
|
||||||
|
teams: {
|
||||||
|
allTeams: [team],
|
||||||
|
current: team,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const board = {...createBoard(), title: 'test board'}
|
||||||
|
|
||||||
|
mockedOctoClient.getBlocksWithBlockID.mockResolvedValueOnce([])
|
||||||
|
mockedOctoClient.getBoard.mockResolvedValueOnce(board)
|
||||||
|
|
||||||
|
const component = (
|
||||||
|
<ReduxProvider store={store}>
|
||||||
|
{wrapIntl(
|
||||||
|
<BoardsUnfurl
|
||||||
|
embed={{data: JSON.stringify({workspaceID: 'foo', cardID: 'invalidCard', boardID: board.id, readToken: 'abc', originalPath: '/test'})}}
|
||||||
|
/>,
|
||||||
|
)}
|
||||||
|
</ReduxProvider>
|
||||||
|
)
|
||||||
|
|
||||||
|
let container: Element | DocumentFragment | null = null
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
const result = render(component)
|
||||||
|
container = result.container
|
||||||
|
})
|
||||||
|
expect(mockedOctoClient.getBoard).toBeCalledWith(board.id)
|
||||||
|
expect(mockedOctoClient.getBlocksWithBlockID).toBeCalledWith('invalidCard', board.id, 'abc')
|
||||||
|
|
||||||
|
expect(container).toMatchSnapshot()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ export const BoardsUnfurl = (props: Props): JSX.Element => {
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
const [firstCard] = cards as Card[]
|
const [firstCard] = cards as Card[]
|
||||||
if (!firstCard || !fetchedBoard) {
|
if (!firstCard || !fetchedBoard || firstCard.type !== 'card') {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -116,7 +116,7 @@ export const BoardsUnfurl = (props: Props): JSX.Element => {
|
|||||||
useWebsockets(currentTeamId, (wsClient: WSClient) => {
|
useWebsockets(currentTeamId, (wsClient: WSClient) => {
|
||||||
const onChangeHandler = (_: WSClient, blocks: Block[]): void => {
|
const onChangeHandler = (_: WSClient, blocks: Block[]): void => {
|
||||||
const cardBlock: Block|undefined = blocks.find((b) => b.id === cardID)
|
const cardBlock: Block|undefined = blocks.find((b) => b.id === cardID)
|
||||||
if (cardBlock && !cardBlock.deleteAt) {
|
if (cardBlock && !cardBlock.deleteAt && cardBlock.type === 'card') {
|
||||||
setCard(cardBlock as Card)
|
setCard(cardBlock as Card)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ describe('components/calculations/Calculation', () => {
|
|||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should match snapshot - option change', () => {
|
test('should match snapshot - option change', async () => {
|
||||||
const onMenuOpen = jest.fn()
|
const onMenuOpen = jest.fn()
|
||||||
const onMenuClose = jest.fn()
|
const onMenuClose = jest.fn()
|
||||||
const onChange = jest.fn()
|
const onChange = jest.fn()
|
||||||
@@ -171,7 +171,7 @@ describe('components/calculations/Calculation', () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const countMenuOption = container.querySelector('#react-select-2-option-1')
|
const countMenuOption = container.querySelector('#react-select-2-option-1')
|
||||||
userEvent.click(countMenuOption as Element)
|
await userEvent.click(countMenuOption as Element)
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
expect(onMenuOpen).not.toBeCalled()
|
expect(onMenuOpen).not.toBeCalled()
|
||||||
expect(onMenuClose).toBeCalled()
|
expect(onMenuClose).toBeCalled()
|
||||||
|
|||||||
@@ -2,9 +2,8 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import Select, {components, DropdownIndicatorProps} from 'react-select'
|
import Select, {components, DropdownIndicatorProps, StylesConfig} from 'react-select'
|
||||||
|
|
||||||
import {CSSObject} from '@emotion/serialize'
|
|
||||||
|
|
||||||
import {useIntl, IntlShape} from 'react-intl'
|
import {useIntl, IntlShape} from 'react-intl'
|
||||||
|
|
||||||
@@ -114,22 +113,22 @@ function generateTypesByOption(): Map<string, string[]> {
|
|||||||
return mapping
|
return mapping
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseStyles = getSelectBaseStyle()
|
const baseStyles = getSelectBaseStyle<Option>()
|
||||||
|
|
||||||
const styles = {
|
const styles: StylesConfig<Option> = {
|
||||||
...baseStyles,
|
...baseStyles,
|
||||||
dropdownIndicator: (provided: CSSObject): CSSObject => ({
|
dropdownIndicator: (...props) => ({
|
||||||
...baseStyles.dropdownIndicator(provided),
|
...baseStyles.dropdownIndicator?.(...props),
|
||||||
pointerEvents: 'none',
|
pointerEvents: 'none',
|
||||||
}),
|
}),
|
||||||
control: (): CSSObject => ({
|
control: () => ({
|
||||||
border: 0,
|
border: 0,
|
||||||
width: '100%',
|
width: '100%',
|
||||||
margin: '0',
|
margin: '0',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
}),
|
}),
|
||||||
menu: (provided: CSSObject): CSSObject => ({
|
menu: (provided) => ({
|
||||||
...provided,
|
...provided,
|
||||||
minWidth: '100%',
|
minWidth: '100%',
|
||||||
width: 'max-content',
|
width: 'max-content',
|
||||||
@@ -137,15 +136,15 @@ const styles = {
|
|||||||
left: '0',
|
left: '0',
|
||||||
marginBottom: '0',
|
marginBottom: '0',
|
||||||
}),
|
}),
|
||||||
singleValue: (provided: CSSObject): CSSObject => ({
|
singleValue: (...props) => ({
|
||||||
...baseStyles.singleValue(provided),
|
...baseStyles.singleValue?.(...props),
|
||||||
opacity: '0.8',
|
opacity: '0.8',
|
||||||
fontSize: '12px',
|
fontSize: '12px',
|
||||||
right: '0',
|
right: '0',
|
||||||
textTransform: 'uppercase',
|
textTransform: 'uppercase',
|
||||||
}),
|
}),
|
||||||
valueContainer: (provided: CSSObject): CSSObject => ({
|
valueContainer: (...props) => ({
|
||||||
...baseStyles.valueContainer(provided),
|
...baseStyles.valueContainer?.(...props),
|
||||||
display: 'none',
|
display: 'none',
|
||||||
pointerEvents: 'none',
|
pointerEvents: 'none',
|
||||||
}),
|
}),
|
||||||
@@ -178,7 +177,7 @@ export const CalculationOptions = (props: BaseCalculationOptionProps): JSX.Eleme
|
|||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select<Option>
|
||||||
styles={styles}
|
styles={styles}
|
||||||
value={Options[props.value]}
|
value={Options[props.value]}
|
||||||
isMulti={false}
|
isMulti={false}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import {render} from '@testing-library/react'
|
|||||||
import {Provider as ReduxProvider} from 'react-redux'
|
import {Provider as ReduxProvider} from 'react-redux'
|
||||||
|
|
||||||
import {TestBlockFactory} from 'src/test/testBlockFactory'
|
import {TestBlockFactory} from 'src/test/testBlockFactory'
|
||||||
import '@testing-library/jest-dom'
|
|
||||||
import {wrapIntl, mockStateStore} from 'src/testUtils'
|
import {wrapIntl, mockStateStore} from 'src/testUtils'
|
||||||
import {IPropertyTemplate} from 'src/blocks/board'
|
import {IPropertyTemplate} from 'src/blocks/board'
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import '@testing-library/jest-dom'
|
|
||||||
import {act, render} from '@testing-library/react'
|
import {act, render} from '@testing-library/react'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {Provider as ReduxProvider} from 'react-redux'
|
import {Provider as ReduxProvider} from 'react-redux'
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import React from 'react'
|
|||||||
import {Provider as ReduxProvider} from 'react-redux'
|
import {Provider as ReduxProvider} from 'react-redux'
|
||||||
|
|
||||||
import {render, screen} from '@testing-library/react'
|
import {render, screen} from '@testing-library/react'
|
||||||
import '@testing-library/jest-dom'
|
|
||||||
|
|
||||||
import {TestBlockFactory} from 'src/test/testBlockFactory'
|
import {TestBlockFactory} from 'src/test/testBlockFactory'
|
||||||
import {blocksById, mockStateStore, wrapDNDIntl} from 'src/testUtils'
|
import {blocksById, mockStateStore, wrapDNDIntl} from 'src/testUtils'
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ exports[`components/cardDetail/CardDetail should render hidden view if limited 1
|
|||||||
<p
|
<p
|
||||||
class="CardDetail__limited-body"
|
class="CardDetail__limited-body"
|
||||||
>
|
>
|
||||||
Upgrade to our Professional or Enterprise plan to view archived cards, have unlimited views per boards, unlimited cards and more.
|
Upgrade to our Professional or Enterprise plan.
|
||||||
<br />
|
<br />
|
||||||
<a
|
<a
|
||||||
class="CardDetail__limited-link"
|
class="CardDetail__limited-link"
|
||||||
@@ -480,7 +480,7 @@ exports[`components/cardDetail/CardDetail should show add properties tour tip 1`
|
|||||||
<div
|
<div
|
||||||
class="tutorial-tour-tip__body"
|
class="tutorial-tour-tip__body"
|
||||||
>
|
>
|
||||||
Add various properties to cards to make them more powerful!
|
Add various properties to cards to make them more powerful.
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="tutorial-tour-tip__image"
|
class="tutorial-tour-tip__image"
|
||||||
|
|||||||
@@ -437,7 +437,7 @@ exports[`components/cardDetail/cardDetailContentsMenu return cardDetailContentsM
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-label="menuwrapper"
|
aria-label="menuwrapper"
|
||||||
class="MenuWrapper override menuOpened"
|
class="MenuWrapper"
|
||||||
role="button"
|
role="button"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
@@ -448,198 +448,6 @@ exports[`components/cardDetail/cardDetailContentsMenu return cardDetailContentsM
|
|||||||
Add content
|
Add content
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
<div
|
|
||||||
class="Menu noselect top "
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="menu-contents"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="menu-options"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
aria-label="text"
|
|
||||||
class="MenuOption TextOption menu-option"
|
|
||||||
role="button"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="d-flex"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="menu-option__icon"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
class="TextIcon Icon"
|
|
||||||
viewBox="0 0 448 512"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M432 416H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-128H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-128H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-128H16A16 16 0 0 0 0 48v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16V48a16 16 0 0 0-16-16z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="menu-option__content"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="menu-name"
|
|
||||||
>
|
|
||||||
text
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="noicon"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
aria-label="image"
|
|
||||||
class="MenuOption TextOption menu-option"
|
|
||||||
role="button"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="d-flex"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="menu-option__icon"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
class="ImageIcon Icon"
|
|
||||||
viewBox="0 0 512 512"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M464 64H48C21.49 64 0 85.49 0 112v288c0 26.51 21.49 48 48 48h416c26.51 0 48-21.49 48-48V112c0-26.51-21.49-48-48-48zm-6 336H54a6 6 0 0 1-6-6V118a6 6 0 0 1 6-6h404a6 6 0 0 1 6 6v276a6 6 0 0 1-6 6zM128 152c-22.091 0-40 17.909-40 40s17.909 40 40 40 40-17.909 40-40-17.909-40-40-40zM96 352h320v-80l-87.515-87.515c-4.686-4.686-12.284-4.686-16.971 0L192 304l-39.515-39.515c-4.686-4.686-12.284-4.686-16.971 0L96 304v48z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="menu-option__content"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="menu-name"
|
|
||||||
>
|
|
||||||
image
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="noicon"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
aria-label="divider"
|
|
||||||
class="MenuOption TextOption menu-option"
|
|
||||||
role="button"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="d-flex"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="menu-option__icon"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
class="DividerIcon Icon"
|
|
||||||
viewBox="0 0 448 512"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M 432,224 H 16 c -8.836556,0 -16,7.16344 -16,16 v 32 c 0,8.83656 7.163444,16 16,16 h 416 c 8.83656,0 16,-7.16344 16,-16 v -32 c 0,-8.83656 -7.16344,-16 -16,-16 z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="menu-option__content"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="menu-name"
|
|
||||||
>
|
|
||||||
divider
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="noicon"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
aria-label="checkbox"
|
|
||||||
class="MenuOption TextOption menu-option"
|
|
||||||
role="button"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="d-flex"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="menu-option__icon"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
class="CheckIcon Icon"
|
|
||||||
viewBox="0 0 100 100"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<polyline
|
|
||||||
points="20,60 40,80 80,40"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="menu-option__content"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="menu-name"
|
|
||||||
>
|
|
||||||
checkbox
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="noicon"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="menu-spacer hideOnWidescreen"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
class="menu-options hideOnWidescreen"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
aria-label="Cancel"
|
|
||||||
class="MenuOption TextOption menu-option menu-cancel"
|
|
||||||
role="button"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="d-flex"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="noicon"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="menu-option__content"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="menu-name"
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="noicon"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ exports[`components/cardDetail/CommentsList comments show up 1`] = `
|
|||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
class="comment-avatar"
|
class="comment-avatar"
|
||||||
src="data:image/svg+xml,<svg xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"0 0 100 100\\" style=\\"fill: rgb(192, 192, 192);\\"><rect width=\\"100\\" height=\\"100\\" /></svg>"
|
src="data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" style="fill: rgb(192, 192, 192);"><rect width="100" height="100" /></svg>"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="MarkdownEditor octo-editor newcomment "
|
class="MarkdownEditor octo-editor newcomment "
|
||||||
@@ -34,7 +34,7 @@ exports[`components/cardDetail/CommentsList comments show up 1`] = `
|
|||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
class="comment-avatar"
|
class="comment-avatar"
|
||||||
src="data:image/svg+xml,<svg xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"0 0 100 100\\" style=\\"fill: rgb(192, 192, 192);\\"><rect width=\\"100\\" height=\\"100\\" /></svg>"
|
src="data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" style="fill: rgb(192, 192, 192);"><rect width="100" height="100" /></svg>"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="comment-username"
|
class="comment-username"
|
||||||
@@ -80,7 +80,7 @@ exports[`components/cardDetail/CommentsList comments show up 1`] = `
|
|||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
class="comment-avatar"
|
class="comment-avatar"
|
||||||
src="data:image/svg+xml,<svg xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"0 0 100 100\\" style=\\"fill: rgb(192, 192, 192);\\"><rect width=\\"100\\" height=\\"100\\" /></svg>"
|
src="data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" style="fill: rgb(192, 192, 192);"><rect width="100" height="100" /></svg>"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="comment-username"
|
class="comment-username"
|
||||||
@@ -138,7 +138,7 @@ exports[`components/cardDetail/CommentsList comments show up in readonly mode 1`
|
|||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
class="comment-avatar"
|
class="comment-avatar"
|
||||||
src="data:image/svg+xml,<svg xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"0 0 100 100\\" style=\\"fill: rgb(192, 192, 192);\\"><rect width=\\"100\\" height=\\"100\\" /></svg>"
|
src="data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" style="fill: rgb(192, 192, 192);"><rect width="100" height="100" /></svg>"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="comment-username"
|
class="comment-username"
|
||||||
@@ -170,7 +170,7 @@ exports[`components/cardDetail/CommentsList comments show up in readonly mode 1`
|
|||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
class="comment-avatar"
|
class="comment-avatar"
|
||||||
src="data:image/svg+xml,<svg xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"0 0 100 100\\" style=\\"fill: rgb(192, 192, 192);\\"><rect width=\\"100\\" height=\\"100\\" /></svg>"
|
src="data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" style="fill: rgb(192, 192, 192);"><rect width="100" height="100" /></svg>"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="comment-username"
|
class="comment-username"
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import CardDetail from './cardDetail'
|
|||||||
global.fetch = FetchMock.fn
|
global.fetch = FetchMock.fn
|
||||||
jest.mock('src/octoClient')
|
jest.mock('src/octoClient')
|
||||||
|
|
||||||
const mockedOctoClient = mocked(octoClient, true)
|
const mockedOctoClient = mocked(octoClient)
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
FetchMock.fn.mockReset()
|
FetchMock.fn.mockReset()
|
||||||
@@ -299,7 +299,7 @@ describe('components/cardDetail/CardDetail', () => {
|
|||||||
expect(nextBtn).toBeDefined()
|
expect(nextBtn).toBeDefined()
|
||||||
expect(nextBtn).not.toBeNull()
|
expect(nextBtn).not.toBeNull()
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
userEvent.click(nextBtn!)
|
await userEvent.click(nextBtn!)
|
||||||
})
|
})
|
||||||
expect(mockedOctoClient.patchUserConfig).toBeCalledWith(
|
expect(mockedOctoClient.patchUserConfig).toBeCalledWith(
|
||||||
'user_id_1',
|
'user_id_1',
|
||||||
@@ -408,7 +408,7 @@ describe('components/cardDetail/CardDetail', () => {
|
|||||||
expect(nextBtn).toBeDefined()
|
expect(nextBtn).toBeDefined()
|
||||||
expect(nextBtn).not.toBeNull()
|
expect(nextBtn).not.toBeNull()
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
userEvent.click(nextBtn!)
|
await userEvent.click(nextBtn!)
|
||||||
})
|
})
|
||||||
expect(mockedOctoClient.patchUserConfig).toBeCalledWith(
|
expect(mockedOctoClient.patchUserConfig).toBeCalledWith(
|
||||||
'user_id_1',
|
'user_id_1',
|
||||||
@@ -521,7 +521,7 @@ describe('components/cardDetail/CardDetail', () => {
|
|||||||
expect(nextBtn).toBeDefined()
|
expect(nextBtn).toBeDefined()
|
||||||
expect(nextBtn).not.toBeNull()
|
expect(nextBtn).not.toBeNull()
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
userEvent.click(nextBtn!)
|
await userEvent.click(nextBtn!)
|
||||||
})
|
})
|
||||||
expect(mockedOctoClient.patchUserConfig).toBeCalledWith(
|
expect(mockedOctoClient.patchUserConfig).toBeCalledWith(
|
||||||
'user_id_1',
|
'user_id_1',
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
import {act, render, screen} from '@testing-library/react'
|
import {render, screen} from '@testing-library/react'
|
||||||
import userEvent from '@testing-library/user-event'
|
|
||||||
import React, {ReactElement, ReactNode} from 'react'
|
import React, {ReactElement, ReactNode} from 'react'
|
||||||
import {Provider as ReduxProvider} from 'react-redux'
|
import {Provider as ReduxProvider} from 'react-redux'
|
||||||
|
|
||||||
import {wrapIntl, mockStateStore} from 'src/testUtils'
|
import userEvent from '@testing-library/user-event'
|
||||||
|
|
||||||
|
import {wrapIntl, mockStateStore, setup} from 'src/testUtils'
|
||||||
|
|
||||||
import {TestBlockFactory} from 'src/test/testBlockFactory'
|
import {TestBlockFactory} from 'src/test/testBlockFactory'
|
||||||
|
|
||||||
@@ -36,22 +37,18 @@ describe('components/cardDetail/cardDetailContentsMenu', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
})
|
})
|
||||||
test('return cardDetailContentsMenu', () => {
|
test('return cardDetailContentsMenu', async () => {
|
||||||
const {container} = render(wrap(<CardDetailContentsMenu/>))
|
const {container} = render(wrap(<CardDetailContentsMenu/>))
|
||||||
const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})
|
const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})
|
||||||
userEvent.click(buttonElement)
|
await userEvent.click(buttonElement)
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('return cardDetailContentsMenu and add Text content', async () => {
|
test('return cardDetailContentsMenu and add Text content', async () => {
|
||||||
const {container} = render(wrap(<CardDetailContentsMenu/>))
|
const {user, container} = setup(wrap(<CardDetailContentsMenu/>))
|
||||||
const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})
|
await user.click(screen.getByRole('button', {name: 'menuwrapper'}))
|
||||||
userEvent.click(buttonElement)
|
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
await act(async () => {
|
await user.click(screen.getByRole('button', {name: 'text'}))
|
||||||
const buttonAddText = screen.getByRole('button', {name: 'text'})
|
|
||||||
userEvent.click(buttonAddText)
|
|
||||||
})
|
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import {
|
|||||||
} from '@testing-library/react'
|
} from '@testing-library/react'
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
import {mocked} from 'jest-mock'
|
import {mocked} from 'jest-mock'
|
||||||
import '@testing-library/jest-dom'
|
|
||||||
import {createIntl} from 'react-intl'
|
import {createIntl} from 'react-intl'
|
||||||
|
|
||||||
import configureStore from 'redux-mock-store'
|
import configureStore from 'redux-mock-store'
|
||||||
@@ -25,7 +24,7 @@ import {PropertyType} from 'src/properties/types'
|
|||||||
import CardDetailProperties from './cardDetailProperties'
|
import CardDetailProperties from './cardDetailProperties'
|
||||||
|
|
||||||
jest.mock('src/mutator')
|
jest.mock('src/mutator')
|
||||||
const mockedMutator = mocked(mutator, true)
|
const mockedMutator = mocked(mutator)
|
||||||
|
|
||||||
describe('components/cardDetail/CardDetailProperties', () => {
|
describe('components/cardDetail/CardDetailProperties', () => {
|
||||||
const board = TestBlockFactory.createBoard()
|
const board = TestBlockFactory.createBoard()
|
||||||
@@ -140,25 +139,25 @@ describe('components/cardDetail/CardDetailProperties', () => {
|
|||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should show confirmation dialog when deleting existing select property', () => {
|
it('should show confirmation dialog when deleting existing select property', async () => {
|
||||||
renderComponent()
|
renderComponent()
|
||||||
|
|
||||||
const menuElement = screen.getByRole('button', {name: 'Owner'})
|
const menuElement = screen.getByRole('button', {name: 'Owner'})
|
||||||
userEvent.click(menuElement)
|
await act(() => userEvent.click(menuElement))
|
||||||
|
|
||||||
const deleteButton = screen.getByRole('button', {name: /delete/i})
|
const deleteButton = screen.getByRole('button', {name: /delete/i})
|
||||||
userEvent.click(deleteButton)
|
await act(() => userEvent.click(deleteButton))
|
||||||
|
|
||||||
expect(screen.getByRole('heading', {name: 'Confirm delete property'})).toBeInTheDocument()
|
expect(screen.getByRole('heading', {name: 'Confirm delete property'})).toBeInTheDocument()
|
||||||
expect(screen.getByRole('button', {name: /delete/i})).toBeInTheDocument()
|
expect(screen.getByRole('button', {name: /delete/i})).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should show property types menu', () => {
|
it('should show property types menu', async () => {
|
||||||
const intl = createIntl({locale: 'en'})
|
const intl = createIntl({locale: 'en'})
|
||||||
const {container} = renderComponent()
|
const {container} = renderComponent()
|
||||||
|
|
||||||
const menuElement = screen.getByRole('button', {name: /add a property/i})
|
const menuElement = screen.getByRole('button', {name: /add a property/i})
|
||||||
userEvent.click(menuElement)
|
await act(() => userEvent.click(menuElement))
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
|
|
||||||
const selectProperty = screen.getByText(/select property type/i)
|
const selectProperty = screen.getByText(/select property type/i)
|
||||||
@@ -170,11 +169,11 @@ describe('components/cardDetail/CardDetailProperties', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should allow change property types menu, confirm', () => {
|
it('should allow change property types menu, confirm', async () => {
|
||||||
renderComponent()
|
renderComponent()
|
||||||
|
|
||||||
const menuElement = screen.getByRole('button', {name: 'Owner'})
|
const menuElement = screen.getByRole('button', {name: 'Owner'})
|
||||||
userEvent.click(menuElement)
|
await act(() => userEvent.click(menuElement))
|
||||||
|
|
||||||
const typeProperty = screen.getByText(/Type: Select/i)
|
const typeProperty = screen.getByText(/Type: Select/i)
|
||||||
expect(typeProperty).toBeInTheDocument()
|
expect(typeProperty).toBeInTheDocument()
|
||||||
@@ -182,7 +181,7 @@ describe('components/cardDetail/CardDetailProperties', () => {
|
|||||||
fireEvent.mouseOver(typeProperty)
|
fireEvent.mouseOver(typeProperty)
|
||||||
|
|
||||||
const newTypeMenu = screen.getByRole('button', {name: 'Text'})
|
const newTypeMenu = screen.getByRole('button', {name: 'Text'})
|
||||||
userEvent.click(newTypeMenu)
|
await act(() => userEvent.click(newTypeMenu))
|
||||||
|
|
||||||
expect(screen.getByRole('heading', {name: 'Confirm property type change'})).toBeInTheDocument()
|
expect(screen.getByRole('heading', {name: 'Confirm property type change'})).toBeInTheDocument()
|
||||||
expect(screen.getByRole('button', {name: /Change property/i})).toBeInTheDocument()
|
expect(screen.getByRole('button', {name: /Change property/i})).toBeInTheDocument()
|
||||||
@@ -192,7 +191,7 @@ describe('components/cardDetail/CardDetailProperties', () => {
|
|||||||
const result = renderComponent()
|
const result = renderComponent()
|
||||||
|
|
||||||
// rename to "Owner-Renamed"
|
// rename to "Owner-Renamed"
|
||||||
onPropertyRenameNoConfirmationDialog(result.container)
|
await onPropertyRenameNoConfirmationDialog(result.container)
|
||||||
const propertyTemplate = board.cardProperties[0]
|
const propertyTemplate = board.cardProperties[0]
|
||||||
|
|
||||||
// should be called once on confirming renaming the property
|
// should be called once on confirming renaming the property
|
||||||
@@ -204,12 +203,10 @@ describe('components/cardDetail/CardDetailProperties', () => {
|
|||||||
renderComponent()
|
renderComponent()
|
||||||
|
|
||||||
const menuElement = screen.getByRole('button', {name: /add a property/i})
|
const menuElement = screen.getByRole('button', {name: /add a property/i})
|
||||||
userEvent.click(menuElement)
|
await act(() => userEvent.click(menuElement))
|
||||||
|
const numberType = screen.getByRole('button', {name: /number/i})
|
||||||
|
|
||||||
await act(async () => {
|
await act( () => userEvent.click(numberType))
|
||||||
const numberType = screen.getByRole('button', {name: /number/i})
|
|
||||||
userEvent.click(numberType)
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(mockedMutator.insertPropertyTemplate).toHaveBeenCalledTimes(1)
|
expect(mockedMutator.insertPropertyTemplate).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
@@ -220,11 +217,11 @@ describe('components/cardDetail/CardDetailProperties', () => {
|
|||||||
expect(template!.type).toBe('number')
|
expect(template!.type).toBe('number')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('confirmation on delete dialog should delete the property', () => {
|
it('confirmation on delete dialog should delete the property', async () => {
|
||||||
const result = renderComponent()
|
const result = renderComponent()
|
||||||
const container = result.container
|
const container = result.container
|
||||||
|
|
||||||
openDeleteConfirmationDialog(container)
|
await openDeleteConfirmationDialog(container)
|
||||||
|
|
||||||
const propertyTemplate = board.cardProperties[0]
|
const propertyTemplate = board.cardProperties[0]
|
||||||
|
|
||||||
@@ -232,48 +229,49 @@ describe('components/cardDetail/CardDetailProperties', () => {
|
|||||||
expect(confirmButton).toBeDefined()
|
expect(confirmButton).toBeDefined()
|
||||||
|
|
||||||
//click delete button
|
//click delete button
|
||||||
userEvent.click(confirmButton!)
|
await act(() => userEvent.click(confirmButton!))
|
||||||
|
|
||||||
// should be called once on confirming delete
|
// should be called once on confirming delete
|
||||||
expect(mockedMutator.deleteProperty).toBeCalledTimes(1)
|
expect(mockedMutator.deleteProperty).toBeCalledTimes(1)
|
||||||
expect(mockedMutator.deleteProperty).toBeCalledWith(board, views, cards, propertyTemplate.id)
|
expect(mockedMutator.deleteProperty).toBeCalledWith(board, views, cards, propertyTemplate.id)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('cancel on delete dialog should do nothing', () => {
|
it('cancel on delete dialog should do nothing', async () => {
|
||||||
const result = renderComponent()
|
const result = renderComponent()
|
||||||
const container = result.container
|
const container = result.container
|
||||||
|
|
||||||
openDeleteConfirmationDialog(container)
|
await openDeleteConfirmationDialog(container)
|
||||||
|
|
||||||
const cancelButton = result.getByTitle('Cancel')
|
const cancelButton = result.getByTitle('Cancel')
|
||||||
expect(cancelButton).toBeDefined()
|
expect(cancelButton).toBeDefined()
|
||||||
|
|
||||||
userEvent.click(cancelButton!)
|
await act(() => userEvent.click(cancelButton!))
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
function openDeleteConfirmationDialog(container: HTMLElement) {
|
async function openDeleteConfirmationDialog(container: HTMLElement) {
|
||||||
const propertyLabel = container.querySelector('.MenuWrapper')
|
const propertyLabel = container.querySelector('.MenuWrapper')
|
||||||
expect(propertyLabel).toBeDefined()
|
expect(propertyLabel).toBeDefined()
|
||||||
userEvent.click(propertyLabel!)
|
await act(() => userEvent.click(propertyLabel!))
|
||||||
|
|
||||||
const deleteOption = container.querySelector('.MenuOption.TextOption')
|
const deleteOption = container.querySelector('.MenuOption.TextOption')
|
||||||
expect(propertyLabel).toBeDefined()
|
expect(propertyLabel).toBeDefined()
|
||||||
userEvent.click(deleteOption!)
|
await act(() => userEvent.click(deleteOption!))
|
||||||
|
|
||||||
const confirmDialog = container.querySelector('.dialog.confirmation-dialog-box')
|
const confirmDialog = container.querySelector('.dialog.confirmation-dialog-box')
|
||||||
expect(confirmDialog).toBeDefined()
|
expect(confirmDialog).toBeDefined()
|
||||||
}
|
}
|
||||||
|
|
||||||
function onPropertyRenameNoConfirmationDialog(container: HTMLElement) {
|
async function onPropertyRenameNoConfirmationDialog(container: HTMLElement) {
|
||||||
const propertyLabel = container.querySelector('.MenuWrapper')
|
const propertyLabel = container.querySelector('.MenuWrapper')
|
||||||
expect(propertyLabel).toBeDefined()
|
expect(propertyLabel).toBeDefined()
|
||||||
userEvent.click(propertyLabel!)
|
await act(() => userEvent.click(propertyLabel!))
|
||||||
|
|
||||||
// write new name in the name text box
|
// write new name in the name text box
|
||||||
const propertyNameInput = container.querySelector('.PropertyMenu.menu-textbox')
|
const propertyNameInput = container.querySelector('.PropertyMenu.menu-textbox')
|
||||||
expect(propertyNameInput).toBeDefined()
|
expect(propertyNameInput).toBeDefined()
|
||||||
userEvent.type(propertyNameInput!, 'Owner - Renamed{enter}')
|
|
||||||
userEvent.click(propertyLabel!)
|
await act(() => userEvent.type(propertyNameInput!, 'Owner - Renamed{enter}', {initialSelectionStart: 0, initialSelectionEnd: 5}))
|
||||||
|
await act(() => userEvent.click(propertyLabel!))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import mutator from 'src/mutator'
|
|||||||
import Comment from './comment'
|
import Comment from './comment'
|
||||||
|
|
||||||
jest.mock('src/mutator')
|
jest.mock('src/mutator')
|
||||||
const mockedMutator = mocked(mutator, true)
|
const mockedMutator = mocked(mutator)
|
||||||
|
|
||||||
const board = TestBlockFactory.createBoard()
|
const board = TestBlockFactory.createBoard()
|
||||||
const card = TestBlockFactory.createCard(board)
|
const card = TestBlockFactory.createCard(board)
|
||||||
@@ -50,7 +50,7 @@ describe('components/cardDetail/comment', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
test('return comment', () => {
|
test('return comment', async () => {
|
||||||
const {container} = render(wrapIntl(
|
const {container} = render(wrapIntl(
|
||||||
<ReduxProvider store={store}>
|
<ReduxProvider store={store}>
|
||||||
<Comment
|
<Comment
|
||||||
@@ -62,7 +62,7 @@ describe('components/cardDetail/comment', () => {
|
|||||||
</ReduxProvider>,
|
</ReduxProvider>,
|
||||||
))
|
))
|
||||||
const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})
|
const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})
|
||||||
userEvent.click(buttonElement)
|
await userEvent.click(buttonElement)
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@ describe('components/cardDetail/comment', () => {
|
|||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('return comment and delete comment', () => {
|
test('return comment and delete comment', async () => {
|
||||||
const {container} = render(wrapIntl(
|
const {container} = render(wrapIntl(
|
||||||
<ReduxProvider store={store}>
|
<ReduxProvider store={store}>
|
||||||
<Comment
|
<Comment
|
||||||
@@ -92,15 +92,15 @@ describe('components/cardDetail/comment', () => {
|
|||||||
</ReduxProvider>,
|
</ReduxProvider>,
|
||||||
))
|
))
|
||||||
const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})
|
const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})
|
||||||
userEvent.click(buttonElement)
|
await userEvent.click(buttonElement)
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
const buttonDelete = screen.getByRole('button', {name: 'Delete'})
|
const buttonDelete = screen.getByRole('button', {name: 'Delete'})
|
||||||
userEvent.click(buttonDelete)
|
await userEvent.click(buttonDelete)
|
||||||
expect(mockedMutator.deleteBlock).toBeCalledTimes(1)
|
expect(mockedMutator.deleteBlock).toBeCalledTimes(1)
|
||||||
expect(mockedMutator.deleteBlock).toBeCalledWith(comment)
|
expect(mockedMutator.deleteBlock).toBeCalledWith(comment)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('return guest comment', () => {
|
test('return guest comment', async () => {
|
||||||
const localStore = mockStateStore([], {users: {boardUsers: {[comment.modifiedBy]: {username: 'username_1', is_guest: true}}}})
|
const localStore = mockStateStore([], {users: {boardUsers: {[comment.modifiedBy]: {username: 'username_1', is_guest: true}}}})
|
||||||
const {container} = render(wrapIntl(
|
const {container} = render(wrapIntl(
|
||||||
<ReduxProvider store={localStore}>
|
<ReduxProvider store={localStore}>
|
||||||
@@ -113,11 +113,11 @@ describe('components/cardDetail/comment', () => {
|
|||||||
</ReduxProvider>,
|
</ReduxProvider>,
|
||||||
))
|
))
|
||||||
const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})
|
const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})
|
||||||
userEvent.click(buttonElement)
|
await userEvent.click(buttonElement)
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('return guest comment readonly', () => {
|
test('return guest comment readonly', async () => {
|
||||||
const localStore = mockStateStore([], {users: {boardUsers: {[comment.modifiedBy]: {username: 'username_1', is_guest: true}}}})
|
const localStore = mockStateStore([], {users: {boardUsers: {[comment.modifiedBy]: {username: 'username_1', is_guest: true}}}})
|
||||||
const {container} = render(wrapIntl(
|
const {container} = render(wrapIntl(
|
||||||
<ReduxProvider store={localStore}>
|
<ReduxProvider store={localStore}>
|
||||||
@@ -132,7 +132,7 @@ describe('components/cardDetail/comment', () => {
|
|||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('return guest comment and delete comment', () => {
|
test('return guest comment and delete comment', async () => {
|
||||||
const localStore = mockStateStore([], {users: {boardUsers: {[comment.modifiedBy]: {username: 'username_1', is_guest: true}}}})
|
const localStore = mockStateStore([], {users: {boardUsers: {[comment.modifiedBy]: {username: 'username_1', is_guest: true}}}})
|
||||||
const {container} = render(wrapIntl(
|
const {container} = render(wrapIntl(
|
||||||
<ReduxProvider store={localStore}>
|
<ReduxProvider store={localStore}>
|
||||||
@@ -145,10 +145,10 @@ describe('components/cardDetail/comment', () => {
|
|||||||
</ReduxProvider>,
|
</ReduxProvider>,
|
||||||
))
|
))
|
||||||
const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})
|
const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})
|
||||||
userEvent.click(buttonElement)
|
await userEvent.click(buttonElement)
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
const buttonDelete = screen.getByRole('button', {name: 'Delete'})
|
const buttonDelete = screen.getByRole('button', {name: 'Delete'})
|
||||||
userEvent.click(buttonDelete)
|
await userEvent.click(buttonDelete)
|
||||||
expect(mockedMutator.deleteBlock).toBeCalledTimes(1)
|
expect(mockedMutator.deleteBlock).toBeCalledTimes(1)
|
||||||
expect(mockedMutator.deleteBlock).toBeCalledWith(comment)
|
expect(mockedMutator.deleteBlock).toBeCalledWith(comment)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import '@testing-library/jest-dom'
|
import {render, screen} from '@testing-library/react'
|
||||||
import {act, render, screen} from '@testing-library/react'
|
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
@@ -23,9 +22,9 @@ jest.mock('src/octoClient')
|
|||||||
jest.mock('src/utils')
|
jest.mock('src/utils')
|
||||||
jest.mock('draft-js/lib/generateRandomKey', () => () => '123')
|
jest.mock('draft-js/lib/generateRandomKey', () => () => '123')
|
||||||
|
|
||||||
const mockedUtils = mocked(Utils, true)
|
const mockedUtils = mocked(Utils)
|
||||||
const mockedMutator = mocked(mutator, true)
|
const mockedMutator = mocked(mutator)
|
||||||
const mockedOctoClient = mocked(octoClient, true)
|
const mockedOctoClient = mocked(octoClient)
|
||||||
mockedUtils.createGuid.mockReturnValue('test-id')
|
mockedUtils.createGuid.mockReturnValue('test-id')
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
@@ -94,135 +93,116 @@ describe('components/cardDialog', () => {
|
|||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
})
|
})
|
||||||
test('should match snapshot', async () => {
|
test('should match snapshot', async () => {
|
||||||
let container
|
|
||||||
await act(async () => {
|
const {container} = render(wrapDNDIntl(
|
||||||
const result = render(wrapDNDIntl(
|
<ReduxProvider store={store}>
|
||||||
<ReduxProvider store={store}>
|
<CardDialog
|
||||||
<CardDialog
|
board={board}
|
||||||
board={board}
|
activeView={boardView}
|
||||||
activeView={boardView}
|
views={[boardView]}
|
||||||
views={[boardView]}
|
cards={[card]}
|
||||||
cards={[card]}
|
cardId={card.id}
|
||||||
cardId={card.id}
|
onClose={jest.fn()}
|
||||||
onClose={jest.fn()}
|
showCard={jest.fn()}
|
||||||
showCard={jest.fn()}
|
readonly={false}
|
||||||
readonly={false}
|
/>
|
||||||
/>
|
</ReduxProvider>,
|
||||||
</ReduxProvider>,
|
))
|
||||||
))
|
|
||||||
container = result.container
|
|
||||||
})
|
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
test('should match snapshot without permissions', async () => {
|
test('should match snapshot without permissions', async () => {
|
||||||
let container
|
|
||||||
const localStore = mockStateStore([], {...state, teams: {current: undefined}})
|
const localStore = mockStateStore([], {...state, teams: {current: undefined}})
|
||||||
await act(async () => {
|
const {container} = render(wrapDNDIntl(
|
||||||
const result = render(wrapDNDIntl(
|
<ReduxProvider store={localStore}>
|
||||||
<ReduxProvider store={localStore}>
|
<CardDialog
|
||||||
<CardDialog
|
board={board}
|
||||||
board={board}
|
activeView={boardView}
|
||||||
activeView={boardView}
|
views={[boardView]}
|
||||||
views={[boardView]}
|
cards={[card]}
|
||||||
cards={[card]}
|
cardId={card.id}
|
||||||
cardId={card.id}
|
onClose={jest.fn()}
|
||||||
onClose={jest.fn()}
|
showCard={jest.fn()}
|
||||||
showCard={jest.fn()}
|
readonly={false}
|
||||||
readonly={false}
|
/>
|
||||||
/>
|
</ReduxProvider>,
|
||||||
</ReduxProvider>,
|
))
|
||||||
))
|
|
||||||
container = result.container
|
|
||||||
})
|
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
test('return a cardDialog readonly', async () => {
|
test('return a cardDialog readonly', async () => {
|
||||||
let container
|
const {container} = render(wrapDNDIntl(
|
||||||
await act(async () => {
|
<ReduxProvider store={store}>
|
||||||
const result = render(wrapDNDIntl(
|
<CardDialog
|
||||||
<ReduxProvider store={store}>
|
board={board}
|
||||||
<CardDialog
|
activeView={boardView}
|
||||||
board={board}
|
views={[boardView]}
|
||||||
activeView={boardView}
|
cards={[card]}
|
||||||
views={[boardView]}
|
cardId={card.id}
|
||||||
cards={[card]}
|
onClose={jest.fn()}
|
||||||
cardId={card.id}
|
showCard={jest.fn()}
|
||||||
onClose={jest.fn()}
|
readonly={true}
|
||||||
showCard={jest.fn()}
|
/>
|
||||||
readonly={true}
|
</ReduxProvider>,
|
||||||
/>
|
))
|
||||||
</ReduxProvider>,
|
|
||||||
))
|
|
||||||
container = result.container
|
|
||||||
})
|
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
test('return cardDialog and do a close action', async () => {
|
test('return cardDialog and do a close action', async () => {
|
||||||
const closeFn = jest.fn()
|
const closeFn = jest.fn()
|
||||||
await act(async () => {
|
render(wrapDNDIntl(
|
||||||
render(wrapDNDIntl(
|
<ReduxProvider store={store}>
|
||||||
<ReduxProvider store={store}>
|
<CardDialog
|
||||||
<CardDialog
|
board={board}
|
||||||
board={board}
|
activeView={boardView}
|
||||||
activeView={boardView}
|
views={[boardView]}
|
||||||
views={[boardView]}
|
cards={[card]}
|
||||||
cards={[card]}
|
cardId={card.id}
|
||||||
cardId={card.id}
|
onClose={closeFn}
|
||||||
onClose={closeFn}
|
showCard={jest.fn()}
|
||||||
showCard={jest.fn()}
|
readonly={false}
|
||||||
readonly={false}
|
/>
|
||||||
/>
|
</ReduxProvider>,
|
||||||
</ReduxProvider>,
|
))
|
||||||
))
|
|
||||||
})
|
|
||||||
const buttonElement = screen.getByRole('button', {name: 'Close dialog'})
|
const buttonElement = screen.getByRole('button', {name: 'Close dialog'})
|
||||||
userEvent.click(buttonElement)
|
await userEvent.click(buttonElement)
|
||||||
expect(closeFn).toBeCalledTimes(1)
|
expect(closeFn).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
test('return cardDialog menu content', async () => {
|
test('return cardDialog menu content', async () => {
|
||||||
let container
|
const {container} = render(wrapDNDIntl(
|
||||||
await act(async () => {
|
<ReduxProvider store={store}>
|
||||||
const result = render(wrapDNDIntl(
|
<CardDialog
|
||||||
<ReduxProvider store={store}>
|
board={board}
|
||||||
<CardDialog
|
activeView={boardView}
|
||||||
board={board}
|
views={[boardView]}
|
||||||
activeView={boardView}
|
cards={[card]}
|
||||||
views={[boardView]}
|
cardId={card.id}
|
||||||
cards={[card]}
|
onClose={jest.fn()}
|
||||||
cardId={card.id}
|
showCard={jest.fn()}
|
||||||
onClose={jest.fn()}
|
readonly={false}
|
||||||
showCard={jest.fn()}
|
/>
|
||||||
readonly={false}
|
</ReduxProvider>,
|
||||||
/>
|
))
|
||||||
</ReduxProvider>,
|
|
||||||
))
|
|
||||||
container = result.container
|
|
||||||
})
|
|
||||||
const buttonMenu = screen.getAllByRole('button', {name: 'menuwrapper'})[0]
|
const buttonMenu = screen.getAllByRole('button', {name: 'menuwrapper'})[0]
|
||||||
userEvent.click(buttonMenu)
|
await userEvent.click(buttonMenu)
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
test('return cardDialog menu content and verify delete action', async () => {
|
test('return cardDialog menu content and verify delete action', async () => {
|
||||||
await act(async () => {
|
render(wrapDNDIntl(
|
||||||
render(wrapDNDIntl(
|
<ReduxProvider store={store}>
|
||||||
<ReduxProvider store={store}>
|
<CardDialog
|
||||||
<CardDialog
|
board={board}
|
||||||
board={board}
|
activeView={boardView}
|
||||||
activeView={boardView}
|
views={[boardView]}
|
||||||
views={[boardView]}
|
cards={[card]}
|
||||||
cards={[card]}
|
cardId={card.id}
|
||||||
cardId={card.id}
|
onClose={jest.fn()}
|
||||||
onClose={jest.fn()}
|
showCard={jest.fn()}
|
||||||
showCard={jest.fn()}
|
readonly={false}
|
||||||
readonly={false}
|
/>
|
||||||
/>
|
</ReduxProvider>,
|
||||||
</ReduxProvider>,
|
))
|
||||||
))
|
|
||||||
})
|
|
||||||
const buttonMenu = screen.getAllByRole('button', {name: 'menuwrapper'})[0]
|
const buttonMenu = screen.getAllByRole('button', {name: 'menuwrapper'})[0]
|
||||||
userEvent.click(buttonMenu)
|
await userEvent.click(buttonMenu)
|
||||||
const buttonDelete = screen.getByRole('button', {name: 'Delete'})
|
const buttonDelete = screen.getByRole('button', {name: 'Delete'})
|
||||||
userEvent.click(buttonDelete)
|
await userEvent.click(buttonDelete)
|
||||||
|
|
||||||
const confirmDialog = screen.getByTitle('Confirmation Dialog Box')
|
const confirmDialog = screen.getByTitle('Confirmation Dialog Box')
|
||||||
expect(confirmDialog).toBeDefined()
|
expect(confirmDialog).toBeDefined()
|
||||||
@@ -231,36 +211,32 @@ describe('components/cardDialog', () => {
|
|||||||
expect(confirmButton).toBeDefined()
|
expect(confirmButton).toBeDefined()
|
||||||
|
|
||||||
//click delete button
|
//click delete button
|
||||||
userEvent.click(confirmButton!)
|
await userEvent.click(confirmButton!)
|
||||||
|
|
||||||
// should be called once on confirming delete
|
// should be called once on confirming delete
|
||||||
expect(mockedMutator.deleteBlock).toBeCalledTimes(1)
|
expect(mockedMutator.deleteBlock).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('return cardDialog menu content and cancel delete confirmation do nothing', async () => {
|
test('return cardDialog menu content and cancel delete confirmation do nothing', async () => {
|
||||||
let container
|
const {container} = render(wrapDNDIntl(
|
||||||
await act(async () => {
|
<ReduxProvider store={store}>
|
||||||
const result = render(wrapDNDIntl(
|
<CardDialog
|
||||||
<ReduxProvider store={store}>
|
board={board}
|
||||||
<CardDialog
|
activeView={boardView}
|
||||||
board={board}
|
views={[boardView]}
|
||||||
activeView={boardView}
|
cards={[card]}
|
||||||
views={[boardView]}
|
cardId={card.id}
|
||||||
cards={[card]}
|
onClose={jest.fn()}
|
||||||
cardId={card.id}
|
showCard={jest.fn()}
|
||||||
onClose={jest.fn()}
|
readonly={false}
|
||||||
showCard={jest.fn()}
|
/>
|
||||||
readonly={false}
|
</ReduxProvider>,
|
||||||
/>
|
))
|
||||||
</ReduxProvider>,
|
|
||||||
))
|
|
||||||
container = result.container
|
|
||||||
})
|
|
||||||
|
|
||||||
const buttonMenu = screen.getAllByRole('button', {name: 'menuwrapper'})[0]
|
const buttonMenu = screen.getAllByRole('button', {name: 'menuwrapper'})[0]
|
||||||
userEvent.click(buttonMenu)
|
await userEvent.click(buttonMenu)
|
||||||
const buttonDelete = screen.getByRole('button', {name: 'Delete'})
|
const buttonDelete = screen.getByRole('button', {name: 'Delete'})
|
||||||
userEvent.click(buttonDelete)
|
await userEvent.click(buttonDelete)
|
||||||
|
|
||||||
const confirmDialog = screen.getByTitle('Confirmation Dialog Box')
|
const confirmDialog = screen.getByTitle('Confirmation Dialog Box')
|
||||||
expect(confirmDialog).toBeDefined()
|
expect(confirmDialog).toBeDefined()
|
||||||
@@ -269,57 +245,53 @@ describe('components/cardDialog', () => {
|
|||||||
expect(cancelButton).toBeDefined()
|
expect(cancelButton).toBeDefined()
|
||||||
|
|
||||||
//click delete button
|
//click delete button
|
||||||
userEvent.click(cancelButton!)
|
await userEvent.click(cancelButton!)
|
||||||
|
|
||||||
// should do nothing on cancel delete dialog
|
// should do nothing on cancel delete dialog
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('return cardDialog menu content and do a New template from card', async () => {
|
test('return cardDialog menu content and do a New template from card', async () => {
|
||||||
await act(async () => {
|
render(wrapDNDIntl(
|
||||||
render(wrapDNDIntl(
|
<ReduxProvider store={store}>
|
||||||
<ReduxProvider store={store}>
|
<CardDialog
|
||||||
<CardDialog
|
board={board}
|
||||||
board={board}
|
activeView={boardView}
|
||||||
activeView={boardView}
|
views={[boardView]}
|
||||||
views={[boardView]}
|
cards={[card]}
|
||||||
cards={[card]}
|
cardId={card.id}
|
||||||
cardId={card.id}
|
onClose={jest.fn()}
|
||||||
onClose={jest.fn()}
|
showCard={jest.fn()}
|
||||||
showCard={jest.fn()}
|
readonly={false}
|
||||||
readonly={false}
|
/>
|
||||||
/>
|
</ReduxProvider>,
|
||||||
</ReduxProvider>,
|
))
|
||||||
))
|
|
||||||
})
|
|
||||||
const buttonMenu = screen.getAllByRole('button', {name: 'menuwrapper'})[0]
|
const buttonMenu = screen.getAllByRole('button', {name: 'menuwrapper'})[0]
|
||||||
userEvent.click(buttonMenu)
|
await userEvent.click(buttonMenu)
|
||||||
const buttonTemplate = screen.getByRole('button', {name: 'New template from card'})
|
const buttonTemplate = screen.getByRole('button', {name: 'New template from card'})
|
||||||
userEvent.click(buttonTemplate)
|
await userEvent.click(buttonTemplate)
|
||||||
expect(mockedMutator.duplicateCard).toBeCalledTimes(1)
|
expect(mockedMutator.duplicateCard).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('return cardDialog menu content and do a copy Link', async () => {
|
test('return cardDialog menu content and do a copy Link', async () => {
|
||||||
await act(async () => {
|
render(wrapDNDIntl(
|
||||||
render(wrapDNDIntl(
|
<ReduxProvider store={store}>
|
||||||
<ReduxProvider store={store}>
|
<CardDialog
|
||||||
<CardDialog
|
board={board}
|
||||||
board={board}
|
activeView={boardView}
|
||||||
activeView={boardView}
|
views={[boardView]}
|
||||||
views={[boardView]}
|
cards={[card]}
|
||||||
cards={[card]}
|
cardId={card.id}
|
||||||
cardId={card.id}
|
onClose={jest.fn()}
|
||||||
onClose={jest.fn()}
|
showCard={jest.fn()}
|
||||||
showCard={jest.fn()}
|
readonly={false}
|
||||||
readonly={false}
|
/>
|
||||||
/>
|
</ReduxProvider>,
|
||||||
</ReduxProvider>,
|
))
|
||||||
))
|
|
||||||
})
|
|
||||||
const buttonMenu = screen.getAllByRole('button', {name: 'menuwrapper'})[0]
|
const buttonMenu = screen.getAllByRole('button', {name: 'menuwrapper'})[0]
|
||||||
userEvent.click(buttonMenu)
|
await userEvent.click(buttonMenu)
|
||||||
const buttonCopy = screen.getByRole('button', {name: 'Copy link'})
|
const buttonCopy = screen.getByRole('button', {name: 'Copy link'})
|
||||||
userEvent.click(buttonCopy)
|
await userEvent.click(buttonCopy)
|
||||||
expect(mockedUtils.copyTextToClipboard).toBeCalledTimes(1)
|
expect(mockedUtils.copyTextToClipboard).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -338,24 +310,21 @@ describe('components/cardDialog', () => {
|
|||||||
|
|
||||||
const newStore = mockStateStore([], newState)
|
const newStore = mockStateStore([], newState)
|
||||||
|
|
||||||
let container
|
const {container} = render(wrapDNDIntl(
|
||||||
await act(async () => {
|
<ReduxProvider store={newStore}>
|
||||||
const result = render(wrapDNDIntl(
|
<CardDialog
|
||||||
<ReduxProvider store={newStore}>
|
board={board}
|
||||||
<CardDialog
|
activeView={boardView}
|
||||||
board={board}
|
views={[boardView]}
|
||||||
activeView={boardView}
|
cards={[card]}
|
||||||
views={[boardView]}
|
cardId={card.id}
|
||||||
cards={[card]}
|
onClose={jest.fn()}
|
||||||
cardId={card.id}
|
showCard={jest.fn()}
|
||||||
onClose={jest.fn()}
|
readonly={false}
|
||||||
showCard={jest.fn()}
|
/>
|
||||||
readonly={false}
|
</ReduxProvider>,
|
||||||
/>
|
))
|
||||||
</ReduxProvider>,
|
|
||||||
))
|
|
||||||
container = result.container
|
|
||||||
})
|
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -373,24 +342,21 @@ describe('components/cardDialog', () => {
|
|||||||
|
|
||||||
const newStore = mockStateStore([], newState)
|
const newStore = mockStateStore([], newState)
|
||||||
|
|
||||||
let container
|
const {container} = render(wrapDNDIntl(
|
||||||
await act(async () => {
|
<ReduxProvider store={newStore}>
|
||||||
const result = render(wrapDNDIntl(
|
<CardDialog
|
||||||
<ReduxProvider store={newStore}>
|
board={board}
|
||||||
<CardDialog
|
activeView={boardView}
|
||||||
board={board}
|
views={[boardView]}
|
||||||
activeView={boardView}
|
cards={[limitedCard]}
|
||||||
views={[boardView]}
|
cardId={limitedCard.id}
|
||||||
cards={[limitedCard]}
|
onClose={jest.fn()}
|
||||||
cardId={limitedCard.id}
|
showCard={jest.fn()}
|
||||||
onClose={jest.fn()}
|
readonly={false}
|
||||||
showCard={jest.fn()}
|
/>
|
||||||
readonly={false}
|
</ReduxProvider>,
|
||||||
/>
|
))
|
||||||
</ReduxProvider>,
|
|
||||||
))
|
|
||||||
container = result.container
|
|
||||||
})
|
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
import {
|
import {
|
||||||
|
act,
|
||||||
fireEvent,
|
fireEvent,
|
||||||
render,
|
render,
|
||||||
screen,
|
screen,
|
||||||
within,
|
within
|
||||||
act
|
|
||||||
} from '@testing-library/react'
|
} from '@testing-library/react'
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
@@ -38,9 +38,9 @@ jest.mock('src/octoClient')
|
|||||||
jest.mock('src/mutator')
|
jest.mock('src/mutator')
|
||||||
jest.mock('src/telemetry/telemetryClient')
|
jest.mock('src/telemetry/telemetryClient')
|
||||||
jest.mock('draft-js/lib/generateRandomKey', () => () => '123')
|
jest.mock('draft-js/lib/generateRandomKey', () => () => '123')
|
||||||
const mockedUtils = mocked(Utils, true)
|
const mockedUtils = mocked(Utils)
|
||||||
const mockedMutator = mocked(Mutator, true)
|
const mockedMutator = mocked(Mutator)
|
||||||
const mockedOctoClient = mocked(octoClient, true)
|
const mockedOctoClient = mocked(octoClient)
|
||||||
mockedUtils.createGuid.mockReturnValue('test-id')
|
mockedUtils.createGuid.mockReturnValue('test-id')
|
||||||
mockedUtils.generateClassName = jest.requireActual('src/utils').Utils.generateClassName
|
mockedUtils.generateClassName = jest.requireActual('src/utils').Utils.generateClassName
|
||||||
describe('components/centerPanel', () => {
|
describe('components/centerPanel', () => {
|
||||||
@@ -234,7 +234,7 @@ describe('components/centerPanel', () => {
|
|||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
describe('return centerPanel and', () => {
|
describe('return centerPanel and', () => {
|
||||||
test('select one card and click background', () => {
|
test('select one card and click background', async () => {
|
||||||
activeView.fields.viewType = 'table'
|
activeView.fields.viewType = 'table'
|
||||||
const {container} = render(wrapDNDIntl(
|
const {container} = render(wrapDNDIntl(
|
||||||
<ReduxProvider store={store}>
|
<ReduxProvider store={store}>
|
||||||
@@ -255,13 +255,13 @@ describe('components/centerPanel', () => {
|
|||||||
//select card
|
//select card
|
||||||
const cardElement = screen.getByRole('textbox', {name: 'card1'})
|
const cardElement = screen.getByRole('textbox', {name: 'card1'})
|
||||||
expect(cardElement).not.toBeNull()
|
expect(cardElement).not.toBeNull()
|
||||||
userEvent.click(cardElement, {shiftKey: true})
|
fireEvent.click(cardElement, {shiftKey: true})
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
|
|
||||||
//background
|
//background
|
||||||
const boardElement = container.querySelector('.BoardComponent')
|
const boardElement = container.querySelector('.BoardComponent')
|
||||||
expect(boardElement).not.toBeNull()
|
expect(boardElement).not.toBeNull()
|
||||||
userEvent.click(boardElement!)
|
fireEvent.click(boardElement!)
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -288,7 +288,7 @@ describe('components/centerPanel', () => {
|
|||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('press touch esc for one card selected', () => {
|
test('press touch esc for one card selected', async () => {
|
||||||
activeView.fields.viewType = 'table'
|
activeView.fields.viewType = 'table'
|
||||||
const {container, baseElement} = render(wrapDNDIntl(
|
const {container, baseElement} = render(wrapDNDIntl(
|
||||||
<ReduxProvider store={store}>
|
<ReduxProvider store={store}>
|
||||||
@@ -306,11 +306,9 @@ describe('components/centerPanel', () => {
|
|||||||
</ReduxProvider>,
|
</ReduxProvider>,
|
||||||
))
|
))
|
||||||
|
|
||||||
act(() => {
|
const cardElement = screen.getByRole('textbox', {name: 'card1'})
|
||||||
const cardElement = screen.getByRole('textbox', {name: 'card1'})
|
expect(cardElement.parentNode).not.toBeNull()
|
||||||
expect(cardElement.parentNode).not.toBeNull()
|
fireEvent.click(cardElement, {shiftKey: true})
|
||||||
userEvent.click(cardElement as HTMLElement, {shiftKey: true})
|
|
||||||
})
|
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
|
|
||||||
//escape
|
//escape
|
||||||
@@ -335,27 +333,23 @@ describe('components/centerPanel', () => {
|
|||||||
</ReduxProvider>,
|
</ReduxProvider>,
|
||||||
))
|
))
|
||||||
|
|
||||||
act(() => {
|
//select card1
|
||||||
//select card1
|
const card1Element = screen.getByRole('textbox', {name: 'card1'})
|
||||||
const card1Element = screen.getByRole('textbox', {name: 'card1'})
|
expect(card1Element).not.toBeNull()
|
||||||
expect(card1Element).not.toBeNull()
|
fireEvent.click(card1Element, {shiftKey: true})
|
||||||
userEvent.click(card1Element, {shiftKey: true})
|
|
||||||
})
|
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
|
|
||||||
act(() => {
|
//select card2
|
||||||
//select card2
|
const card2Element = screen.getByRole('textbox', {name: 'card2'})
|
||||||
const card2Element = screen.getByRole('textbox', {name: 'card2'})
|
expect(card2Element).not.toBeNull()
|
||||||
expect(card2Element).not.toBeNull()
|
fireEvent.click(card2Element, {shiftKey: true, ctrlKey: true})
|
||||||
userEvent.click(card2Element, {shiftKey: true, ctrlKey: true})
|
|
||||||
})
|
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
|
|
||||||
//escape
|
//escape
|
||||||
fireEvent.keyDown(baseElement, {keyCode: 27})
|
fireEvent.keyDown(baseElement, {keyCode: 27})
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
test('press touch del for one card selected', () => {
|
test('press touch del for one card selected', async () => {
|
||||||
activeView.fields.viewType = 'table'
|
activeView.fields.viewType = 'table'
|
||||||
const {container, baseElement} = render(wrapDNDIntl(
|
const {container, baseElement} = render(wrapDNDIntl(
|
||||||
<ReduxProvider store={store}>
|
<ReduxProvider store={store}>
|
||||||
@@ -372,19 +366,18 @@ describe('components/centerPanel', () => {
|
|||||||
/>
|
/>
|
||||||
</ReduxProvider>,
|
</ReduxProvider>,
|
||||||
))
|
))
|
||||||
act(() => {
|
const cardElement = screen.getByRole('textbox', {name: 'card1'})
|
||||||
const cardElement = screen.getByRole('textbox', {name: 'card1'})
|
expect(cardElement).not.toBeNull()
|
||||||
expect(cardElement).not.toBeNull()
|
fireEvent.click(cardElement, {shiftKey: true})
|
||||||
userEvent.click(cardElement, {shiftKey: true})
|
|
||||||
})
|
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
|
|
||||||
//delete
|
//delete
|
||||||
fireEvent.keyDown(baseElement, {keyCode: 8})
|
fireEvent.keyDown(baseElement, {keyCode: 8})
|
||||||
expect(mockedMutator.performAsUndoGroup).toBeCalledTimes(1)
|
expect(mockedMutator.performAsUndoGroup).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
test('press touch ctrl+d for one card selected', () => {
|
test('press touch ctrl+d for one card selected', async () => {
|
||||||
activeView.fields.viewType = 'table'
|
activeView.fields.viewType = 'table'
|
||||||
|
|
||||||
const {container, baseElement} = render(wrapDNDIntl(
|
const {container, baseElement} = render(wrapDNDIntl(
|
||||||
<ReduxProvider store={store}>
|
<ReduxProvider store={store}>
|
||||||
<CenterPanel
|
<CenterPanel
|
||||||
@@ -400,18 +393,16 @@ describe('components/centerPanel', () => {
|
|||||||
/>
|
/>
|
||||||
</ReduxProvider>,
|
</ReduxProvider>,
|
||||||
))
|
))
|
||||||
act(() => {
|
const cardElement = screen.getByRole('textbox', {name: 'card1'})
|
||||||
const cardElement = screen.getByRole('textbox', {name: 'card1'})
|
expect(cardElement).not.toBeNull()
|
||||||
expect(cardElement).not.toBeNull()
|
fireEvent.click(cardElement, {shiftKey: true})
|
||||||
userEvent.click(cardElement, {shiftKey: true})
|
|
||||||
})
|
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
|
|
||||||
//ctrl+d
|
//ctrl+d
|
||||||
fireEvent.keyDown(baseElement, {ctrlKey: true, keyCode: 68})
|
fireEvent.keyDown(baseElement, {ctrlKey: true, keyCode: 68})
|
||||||
expect(mockedMutator.performAsUndoGroup).toBeCalledTimes(1)
|
expect(mockedMutator.performAsUndoGroup).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
test('click on card to show card', () => {
|
test('click on card to show card', async () => {
|
||||||
activeView.fields.viewType = 'board'
|
activeView.fields.viewType = 'board'
|
||||||
const mockedShowCard = jest.fn()
|
const mockedShowCard = jest.fn()
|
||||||
const {container} = render(wrapDNDIntl(
|
const {container} = render(wrapDNDIntl(
|
||||||
@@ -433,11 +424,11 @@ describe('components/centerPanel', () => {
|
|||||||
const kanbanCardElements = container.querySelectorAll('.KanbanCard')
|
const kanbanCardElements = container.querySelectorAll('.KanbanCard')
|
||||||
expect(kanbanCardElements).not.toBeNull()
|
expect(kanbanCardElements).not.toBeNull()
|
||||||
const kanbanCardElement = kanbanCardElements[0]
|
const kanbanCardElement = kanbanCardElements[0]
|
||||||
userEvent.click(kanbanCardElement)
|
await userEvent.click(kanbanCardElement)
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
expect(mockedShowCard).toBeCalledWith(card1.id)
|
expect(mockedShowCard).toBeCalledWith(card1.id)
|
||||||
})
|
})
|
||||||
test('click on new card to add card', () => {
|
test('click on new card to add card', async () => {
|
||||||
activeView.fields.viewType = 'table'
|
activeView.fields.viewType = 'table'
|
||||||
const {container} = render(wrapDNDIntl(
|
const {container} = render(wrapDNDIntl(
|
||||||
<ReduxProvider store={store}>
|
<ReduxProvider store={store}>
|
||||||
@@ -456,10 +447,10 @@ describe('components/centerPanel', () => {
|
|||||||
))
|
))
|
||||||
const buttonWithMenuElement = container.querySelector('.ButtonWithMenu')
|
const buttonWithMenuElement = container.querySelector('.ButtonWithMenu')
|
||||||
expect(buttonWithMenuElement).not.toBeNull()
|
expect(buttonWithMenuElement).not.toBeNull()
|
||||||
userEvent.click(buttonWithMenuElement!)
|
await act(() => userEvent.click(buttonWithMenuElement!))
|
||||||
expect(mockedMutator.performAsUndoGroup).toBeCalledTimes(1)
|
expect(mockedMutator.performAsUndoGroup).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
test('click on new card to add card template', () => {
|
test('click on new card to add card template', async () => {
|
||||||
activeView.fields.viewType = 'table'
|
activeView.fields.viewType = 'table'
|
||||||
const {container} = render(wrapDNDIntl(
|
const {container} = render(wrapDNDIntl(
|
||||||
<ReduxProvider store={store}>
|
<ReduxProvider store={store}>
|
||||||
@@ -478,13 +469,13 @@ describe('components/centerPanel', () => {
|
|||||||
))
|
))
|
||||||
const elementMenuWrapper = container.querySelector('.ButtonWithMenu > div.MenuWrapper')
|
const elementMenuWrapper = container.querySelector('.ButtonWithMenu > div.MenuWrapper')
|
||||||
expect(elementMenuWrapper).not.toBeNull()
|
expect(elementMenuWrapper).not.toBeNull()
|
||||||
userEvent.click(elementMenuWrapper!)
|
await act(() => userEvent.click(elementMenuWrapper!))
|
||||||
const buttonNewTemplate = within(elementMenuWrapper!.parentElement!).getByRole('button', {name: 'New template'})
|
const buttonNewTemplate = within(elementMenuWrapper!.parentElement!).getByRole('button', {name: 'New template'})
|
||||||
userEvent.click(buttonNewTemplate)
|
await act(() => userEvent.click(buttonNewTemplate))
|
||||||
expect(mockedMutator.insertBlock).toBeCalledTimes(1)
|
expect(mockedMutator.insertBlock).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('click on new card to add card from template', () => {
|
test('click on new card to add card from template', async () => {
|
||||||
activeView.fields.viewType = 'table'
|
activeView.fields.viewType = 'table'
|
||||||
activeView.fields.defaultTemplateId = '1'
|
activeView.fields.defaultTemplateId = '1'
|
||||||
const {container} = render(wrapDNDIntl(
|
const {container} = render(wrapDNDIntl(
|
||||||
@@ -504,14 +495,14 @@ describe('components/centerPanel', () => {
|
|||||||
))
|
))
|
||||||
const elementMenuWrapper = container.querySelector('.ButtonWithMenu > div.MenuWrapper')
|
const elementMenuWrapper = container.querySelector('.ButtonWithMenu > div.MenuWrapper')
|
||||||
expect(elementMenuWrapper).not.toBeNull()
|
expect(elementMenuWrapper).not.toBeNull()
|
||||||
userEvent.click(elementMenuWrapper!)
|
await act(() => userEvent.click(elementMenuWrapper!))
|
||||||
const elementCard1 = within(elementMenuWrapper!.parentElement!).getByRole('button', {name: 'card1'})
|
const elementCard1 = within(elementMenuWrapper!.parentElement!).getByRole('button', {name: 'card1'})
|
||||||
expect(elementCard1).not.toBeNull()
|
expect(elementCard1).not.toBeNull()
|
||||||
userEvent.click(elementCard1)
|
await act(() => userEvent.click(elementCard1))
|
||||||
expect(mockedMutator.performAsUndoGroup).toBeCalledTimes(1)
|
expect(mockedMutator.performAsUndoGroup).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('click on new card to edit template', () => {
|
test('click on new card to edit template', async () => {
|
||||||
activeView.fields.viewType = 'table'
|
activeView.fields.viewType = 'table'
|
||||||
activeView.fields.defaultTemplateId = '1'
|
activeView.fields.defaultTemplateId = '1'
|
||||||
const {container} = render(wrapDNDIntl(
|
const {container} = render(wrapDNDIntl(
|
||||||
@@ -531,15 +522,15 @@ describe('components/centerPanel', () => {
|
|||||||
))
|
))
|
||||||
const elementMenuWrapper = container.querySelector('.ButtonWithMenu > div.MenuWrapper')
|
const elementMenuWrapper = container.querySelector('.ButtonWithMenu > div.MenuWrapper')
|
||||||
expect(elementMenuWrapper).not.toBeNull()
|
expect(elementMenuWrapper).not.toBeNull()
|
||||||
userEvent.click(elementMenuWrapper!)
|
await act(() => userEvent.click(elementMenuWrapper!))
|
||||||
const elementCard1 = within(elementMenuWrapper!.parentElement!).getByRole('button', {name: 'card1'})
|
const elementCard1 = within(elementMenuWrapper!.parentElement!).getByRole('button', {name: 'card1'})
|
||||||
expect(elementCard1).not.toBeNull()
|
expect(elementCard1).not.toBeNull()
|
||||||
const elementMenuWrapperCard1 = within(elementCard1).getByRole('button', {name: 'menuwrapper'})
|
const elementMenuWrapperCard1 = within(elementCard1).getByRole('button', {name: 'menuwrapper'})
|
||||||
expect(elementMenuWrapperCard1).not.toBeNull()
|
expect(elementMenuWrapperCard1).not.toBeNull()
|
||||||
userEvent.click(elementMenuWrapperCard1)
|
await act(() => userEvent.click(elementMenuWrapperCard1))
|
||||||
const elementEditMenuTemplate = within(elementMenuWrapperCard1).getByRole('button', {name: 'Edit'})
|
const elementEditMenuTemplate = within(elementMenuWrapperCard1).getByRole('button', {name: 'Edit'})
|
||||||
expect(elementMenuWrapperCard1).not.toBeNull()
|
expect(elementMenuWrapperCard1).not.toBeNull()
|
||||||
userEvent.click(elementEditMenuTemplate)
|
await act(() => userEvent.click(elementEditMenuTemplate))
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {Post} from 'mattermost-redux/types/posts'
|
import {Post} from '@mattermost/types/posts'
|
||||||
|
|
||||||
const PostTypeCloudUpgradeNudge = (props: {post: Post}): JSX.Element => {
|
const PostTypeCloudUpgradeNudge = (props: {post: Post}): JSX.Element => {
|
||||||
const ctaHandler = (e: React.MouseEvent) => {
|
const ctaHandler = (e: React.MouseEvent) => {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
import '@testing-library/jest-dom'
|
|
||||||
import {render} from '@testing-library/react'
|
import {render} from '@testing-library/react'
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
@@ -28,7 +27,7 @@ describe('/components/confirmAddUserForNotifications', () => {
|
|||||||
expect(result.container).toMatchSnapshot()
|
expect(result.container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('confirm button click, run onConfirm Function once', () => {
|
it('confirm button click, run onConfirm Function once', async () => {
|
||||||
const onConfirm = jest.fn()
|
const onConfirm = jest.fn()
|
||||||
|
|
||||||
const result = render(
|
const result = render(
|
||||||
@@ -42,11 +41,11 @@ describe('/components/confirmAddUserForNotifications', () => {
|
|||||||
/>,
|
/>,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
userEvent.click(result.getByTitle('Add to board'))
|
await userEvent.click(result.getByTitle('Add to board'))
|
||||||
expect(onConfirm).toBeCalledTimes(1)
|
expect(onConfirm).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('cancel button click runs onClose function', () => {
|
it('cancel button click runs onClose function', async () => {
|
||||||
const onClose = jest.fn()
|
const onClose = jest.fn()
|
||||||
|
|
||||||
const result = render(
|
const result = render(
|
||||||
@@ -60,7 +59,7 @@ describe('/components/confirmAddUserForNotifications', () => {
|
|||||||
/>,
|
/>,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
userEvent.click(result.getByTitle('Cancel'))
|
await userEvent.click(result.getByTitle('Cancel'))
|
||||||
expect(onClose).toBeCalledTimes(1)
|
expect(onClose).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
import '@testing-library/jest-dom'
|
|
||||||
import {act, render} from '@testing-library/react'
|
import {act, render} from '@testing-library/react'
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
@@ -55,16 +54,16 @@ describe('/components/confirmationDialogBox', () => {
|
|||||||
expect(containerWithCnfrmBtnText).toMatchSnapshot()
|
expect(containerWithCnfrmBtnText).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('confirm button click, run onConfirm Function once', () => {
|
it('confirm button click, run onConfirm Function once', async () => {
|
||||||
const result = render(
|
const result = render(
|
||||||
wrapDNDIntl(<ConfirmationDialogBox dialogBox={dialogProps}/>),
|
wrapDNDIntl(<ConfirmationDialogBox dialogBox={dialogProps}/>),
|
||||||
)
|
)
|
||||||
|
|
||||||
userEvent.click(result.getByTitle('Confirm'))
|
await userEvent.click(result.getByTitle('Confirm'))
|
||||||
expect(dialogProps.onConfirm).toBeCalledTimes(1)
|
expect(dialogProps.onConfirm).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('confirm button (with passed prop text), run onConfirm Function once', () => {
|
it('confirm button (with passed prop text), run onConfirm Function once', async () => {
|
||||||
const resultWithConfirmBtnText = render(
|
const resultWithConfirmBtnText = render(
|
||||||
wrapDNDIntl(
|
wrapDNDIntl(
|
||||||
<ConfirmationDialogBox
|
<ConfirmationDialogBox
|
||||||
@@ -73,21 +72,21 @@ describe('/components/confirmationDialogBox', () => {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
userEvent.click(
|
await userEvent.click(
|
||||||
resultWithConfirmBtnText.getByTitle(dialogPropsWithCnfrmBtnText.confirmButtonText),
|
resultWithConfirmBtnText.getByTitle(dialogPropsWithCnfrmBtnText.confirmButtonText),
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(dialogPropsWithCnfrmBtnText.onConfirm).toBeCalledTimes(1)
|
expect(dialogPropsWithCnfrmBtnText.onConfirm).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('cancel button click runs onClose function', () => {
|
it('cancel button click runs onClose function', async () => {
|
||||||
const result = render(wrapDNDIntl(
|
const result = render(wrapDNDIntl(
|
||||||
<ConfirmationDialogBox
|
<ConfirmationDialogBox
|
||||||
dialogBox={dialogProps}
|
dialogBox={dialogProps}
|
||||||
/>,
|
/>,
|
||||||
))
|
))
|
||||||
|
|
||||||
userEvent.click(result.getByTitle('Cancel'))
|
await userEvent.click(result.getByTitle('Cancel'))
|
||||||
expect(dialogProps.onClose).toBeCalledTimes(1)
|
expect(dialogProps.onClose).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ exports[`components/content/checkboxElement should change title 1`] = `
|
|||||||
placeholder="Edit text..."
|
placeholder="Edit text..."
|
||||||
spellcheck="true"
|
spellcheck="true"
|
||||||
title="new title"
|
title="new title"
|
||||||
type="text"
|
|
||||||
value="new title"
|
value="new title"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {IUser} from 'src/user'
|
|||||||
import AttachmentElement from './attachmentElement'
|
import AttachmentElement from './attachmentElement'
|
||||||
|
|
||||||
jest.mock('src/octoClient')
|
jest.mock('src/octoClient')
|
||||||
const mockedOcto = mocked(octoClient, true)
|
const mockedOcto = mocked(octoClient)
|
||||||
mockedOcto.getFileAsDataUrl.mockResolvedValue({url: 'test.txt'})
|
mockedOcto.getFileAsDataUrl.mockResolvedValue({url: 'test.txt'})
|
||||||
mockedOcto.getFileInfo.mockResolvedValue({
|
mockedOcto.getFileInfo.mockResolvedValue({
|
||||||
name: 'test.txt',
|
name: 'test.txt',
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
screen,
|
screen,
|
||||||
waitFor
|
waitFor
|
||||||
} from '@testing-library/react'
|
} from '@testing-library/react'
|
||||||
import '@testing-library/jest-dom'
|
|
||||||
import {mocked} from 'jest-mock'
|
import {mocked} from 'jest-mock'
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
|
|
||||||
@@ -21,7 +20,7 @@ import mutator from 'src/mutator'
|
|||||||
import CheckboxElement from './checkboxElement'
|
import CheckboxElement from './checkboxElement'
|
||||||
|
|
||||||
jest.mock('src/mutator')
|
jest.mock('src/mutator')
|
||||||
const mockedMutator = mocked(mutator, true)
|
const mockedMutator = mocked(mutator)
|
||||||
|
|
||||||
const board = TestBlockFactory.createBoard()
|
const board = TestBlockFactory.createBoard()
|
||||||
const card = TestBlockFactory.createCard(board)
|
const card = TestBlockFactory.createCard(board)
|
||||||
@@ -84,7 +83,7 @@ describe('components/content/checkboxElement', () => {
|
|||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should change title', () => {
|
it('should change title', async () => {
|
||||||
const {container} = render(wrap(
|
const {container} = render(wrap(
|
||||||
<CheckboxElement
|
<CheckboxElement
|
||||||
block={checkboxBlock}
|
block={checkboxBlock}
|
||||||
@@ -93,8 +92,8 @@ describe('components/content/checkboxElement', () => {
|
|||||||
))
|
))
|
||||||
const newTitle = 'new title'
|
const newTitle = 'new title'
|
||||||
const input = screen.getByRole('textbox', {name: /test-title/i})
|
const input = screen.getByRole('textbox', {name: /test-title/i})
|
||||||
userEvent.clear(input)
|
await userEvent.clear(input)
|
||||||
userEvent.type(input, newTitle)
|
await userEvent.type(input, newTitle)
|
||||||
fireEvent.blur(input)
|
fireEvent.blur(input)
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
expect(mockedMutator.changeBlockTitle).toHaveBeenCalledTimes(1)
|
expect(mockedMutator.changeBlockTitle).toHaveBeenCalledTimes(1)
|
||||||
@@ -106,7 +105,7 @@ describe('components/content/checkboxElement', () => {
|
|||||||
expect.anything())
|
expect.anything())
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should toggle value', () => {
|
it('should toggle value', async () => {
|
||||||
const {container} = render(wrap(
|
const {container} = render(wrap(
|
||||||
<CheckboxElement
|
<CheckboxElement
|
||||||
block={checkboxBlock}
|
block={checkboxBlock}
|
||||||
@@ -114,7 +113,7 @@ describe('components/content/checkboxElement', () => {
|
|||||||
/>,
|
/>,
|
||||||
))
|
))
|
||||||
const input = screen.getByRole('checkbox')
|
const input = screen.getByRole('checkbox')
|
||||||
userEvent.click(input)
|
await userEvent.click(input)
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
expect(mockedMutator.updateBlock).toHaveBeenCalledTimes(1)
|
expect(mockedMutator.updateBlock).toHaveBeenCalledTimes(1)
|
||||||
expect(mockedMutator.updateBlock).toHaveBeenCalledWith(
|
expect(mockedMutator.updateBlock).toHaveBeenCalledWith(
|
||||||
@@ -149,17 +148,17 @@ describe('components/content/checkboxElement', () => {
|
|||||||
const input = screen.getByRole('textbox', {name: /test-title/i})
|
const input = screen.getByRole('textbox', {name: /test-title/i})
|
||||||
|
|
||||||
// should not add new checkbox when current one has empty title
|
// should not add new checkbox when current one has empty title
|
||||||
userEvent.clear(input)
|
await userEvent.clear(input)
|
||||||
userEvent.type(input, '{enter}')
|
await userEvent.type(input, '{Enter}')
|
||||||
expect(addElement).toHaveBeenCalledTimes(0)
|
expect(addElement).toHaveBeenCalledTimes(0)
|
||||||
|
|
||||||
// should add new checkbox when current one has non-empty title
|
// should add new checkbox when current one has non-empty title
|
||||||
userEvent.clear(input)
|
await userEvent.clear(input)
|
||||||
userEvent.type(input, 'new-title{enter}')
|
await userEvent.type(input, 'new-title{Enter}')
|
||||||
await waitFor(() => expect(addElement).toHaveBeenCalledTimes(1))
|
await waitFor(() => expect(addElement).toHaveBeenCalledTimes(1))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should delete automatically added checkbox with empty title on esc/enter pressed', () => {
|
it('should delete automatically added checkbox with empty title on esc/enter pressed', async () => {
|
||||||
const addedBlock = createContentBlock(checkboxBlock)
|
const addedBlock = createContentBlock(checkboxBlock)
|
||||||
addedBlock.title = ''
|
addedBlock.title = ''
|
||||||
const deleteElement = jest.fn()
|
const deleteElement = jest.fn()
|
||||||
@@ -173,15 +172,16 @@ describe('components/content/checkboxElement', () => {
|
|||||||
/>
|
/>
|
||||||
</CardDetailContext.Provider>,
|
</CardDetailContext.Provider>,
|
||||||
))
|
))
|
||||||
|
|
||||||
const input = screen.getByRole('textbox')
|
const input = screen.getByRole('textbox')
|
||||||
userEvent.type(input, '{esc}')
|
|
||||||
|
// should delete if title is empty
|
||||||
|
await userEvent.type(input, '{Escape}')
|
||||||
expect(deleteElement).toHaveBeenCalledTimes(1)
|
expect(deleteElement).toHaveBeenCalledTimes(1)
|
||||||
userEvent.type(input, '{enter}')
|
await userEvent.type(input, '{Enter}')
|
||||||
expect(deleteElement).toHaveBeenCalledTimes(2)
|
expect(deleteElement).toHaveBeenCalledTimes(2)
|
||||||
|
|
||||||
// should not delete if title is not empty
|
// should not delete if title is not empty
|
||||||
userEvent.type(input, 'new-title{esc}')
|
await userEvent.type(input, 'new-title{Enter}')
|
||||||
expect(deleteElement).toHaveBeenCalledTimes(2)
|
expect(deleteElement).toHaveBeenCalledTimes(2)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
import React, {ReactElement, ReactNode} from 'react'
|
import React, {ReactElement, ReactNode} from 'react'
|
||||||
|
|
||||||
import '@testing-library/jest-dom'
|
|
||||||
|
|
||||||
import {render} from '@testing-library/react'
|
import {render} from '@testing-library/react'
|
||||||
|
|
||||||
@@ -54,6 +53,8 @@ describe('components/content/contentElement', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should return null for unknown type', () => {
|
it('should return null for unknown type', () => {
|
||||||
|
jest.spyOn(console, 'error').mockImplementation()
|
||||||
|
|
||||||
const block: ContentBlock = {...contentBlock, type: 'unknown'}
|
const block: ContentBlock = {...contentBlock, type: 'unknown'}
|
||||||
const {container} = render(wrap(
|
const {container} = render(wrap(
|
||||||
<ContentElement
|
<ContentElement
|
||||||
@@ -63,5 +64,6 @@ describe('components/content/contentElement', () => {
|
|||||||
/>,
|
/>,
|
||||||
))
|
))
|
||||||
expect(container).toBeEmptyDOMElement()
|
expect(container).toBeEmptyDOMElement()
|
||||||
|
expect(console.error).toBeCalledWith(expect.stringContaining('ContentElement, unknown content type: unknown'))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import octoClient from 'src/octoClient'
|
|||||||
import ImageElement from './imageElement'
|
import ImageElement from './imageElement'
|
||||||
|
|
||||||
jest.mock('src/octoClient')
|
jest.mock('src/octoClient')
|
||||||
const mockedOcto = mocked(octoClient, true)
|
const mockedOcto = mocked(octoClient)
|
||||||
mockedOcto.getFileAsDataUrl.mockResolvedValue({url: 'test.jpg'})
|
mockedOcto.getFileAsDataUrl.mockResolvedValue({url: 'test.jpg'})
|
||||||
|
|
||||||
describe('components/content/ImageElement', () => {
|
describe('components/content/ImageElement', () => {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import React from 'react'
|
|||||||
import {render, act} from '@testing-library/react'
|
import {render, act} from '@testing-library/react'
|
||||||
import {Provider as ReduxProvider} from 'react-redux'
|
import {Provider as ReduxProvider} from 'react-redux'
|
||||||
|
|
||||||
import '@testing-library/jest-dom'
|
|
||||||
|
|
||||||
import {mocked} from 'jest-mock'
|
import {mocked} from 'jest-mock'
|
||||||
|
|
||||||
@@ -22,7 +21,7 @@ import TextElement from './textElement'
|
|||||||
jest.mock('src/utils')
|
jest.mock('src/utils')
|
||||||
jest.mock('src/mutator')
|
jest.mock('src/mutator')
|
||||||
jest.mock('draft-js/lib/generateRandomKey', () => () => '123')
|
jest.mock('draft-js/lib/generateRandomKey', () => () => '123')
|
||||||
const mockedUtils = mocked(Utils, true)
|
const mockedUtils = mocked(Utils)
|
||||||
mockedUtils.createGuid.mockReturnValue('test-id')
|
mockedUtils.createGuid.mockReturnValue('test-id')
|
||||||
const defaultBlock: TextBlock = {
|
const defaultBlock: TextBlock = {
|
||||||
id: 'test-id',
|
id: 'test-id',
|
||||||
|
|||||||
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Ссылка в новой задаче
Block a user