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*
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
cancel-in-progress: ${{ !contains( github.ref , 'heads/ref/master') }}
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
@@ -150,7 +150,7 @@ jobs:
|
||||
env:
|
||||
NODE_OPTIONS: --max_old_space_size=5120
|
||||
run: |
|
||||
# npm run test-ci --workspace=boards
|
||||
npm run test-ci --workspace=boards
|
||||
npm run test-ci --workspace=channels
|
||||
npm run test-ci --workspace=platform/client
|
||||
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"
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
cancel-in-progress: ${{ !contains( github.ref , 'heads/ref/master') }}
|
||||
jobs:
|
||||
check-mocks:
|
||||
name: Check mocks
|
||||
|
||||
26
.github/workflows/codeql-analysis.yml
поставляемый
26
.github/workflows/codeql-analysis.yml
поставляемый
@@ -2,7 +2,7 @@ name: "CodeQL"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
cancel-in-progress: ${{ !contains( github.ref , 'heads/ref/master') }}
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
@@ -10,7 +10,7 @@ on:
|
||||
branches: [ master ]
|
||||
schedule:
|
||||
- cron: '30 5,17 * * *'
|
||||
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'go' ]
|
||||
language: [ 'go', 'javascript' ]
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
@@ -36,14 +36,26 @@ jobs:
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
debug: false
|
||||
config-file: ./.github/codeql/codeql-config.yml
|
||||
|
||||
- name: Build
|
||||
config-file: ./.github/codeql/codeql-config.yml
|
||||
|
||||
- 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: |
|
||||
cd server
|
||||
make setup-go-work
|
||||
make build-linux-amd64
|
||||
if: ${{ matrix.language == 'go' }}
|
||||
|
||||
# Perform 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*
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
cancel-in-progress: ${{ !contains( github.ref , 'heads/ref/master') }}
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
4
.github/workflows/scorecards-analysis.yml
поставляемый
4
.github/workflows/scorecards-analysis.yml
поставляемый
@@ -5,6 +5,10 @@ on:
|
||||
schedule:
|
||||
- cron: '44 6 * * *'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: ${{ !contains( github.ref , 'heads/ref/master') }}
|
||||
|
||||
# Declare default permissions as read only.
|
||||
permissions: read-all
|
||||
|
||||
|
||||
1
.gitignore
поставляемый
1
.gitignore
поставляемый
@@ -134,6 +134,7 @@ cprofile.out
|
||||
*.test
|
||||
webapp/coverage
|
||||
/report.xml
|
||||
junit.xml
|
||||
|
||||
.agignore
|
||||
.ctags
|
||||
|
||||
@@ -1 +1,9 @@
|
||||
/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');
|
||||
});
|
||||
|
||||
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', () => {
|
||||
// Visit a page and create new empty board
|
||||
cy.visit('/boards');
|
||||
|
||||
@@ -334,7 +334,7 @@ describe('Pricing modal', () => {
|
||||
|
||||
// * Check that enterprise card action button shows Try free for 30 days
|
||||
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', () => {
|
||||
@@ -365,7 +365,7 @@ describe('Pricing modal', () => {
|
||||
|
||||
// * Check that enterprise card action button is disabled
|
||||
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('#start_cloud_trial_btn').should('be.disabled');
|
||||
});
|
||||
|
||||
@@ -102,7 +102,7 @@ describe('Self hosted Pricing modal', () => {
|
||||
// * Check that enteprise trial button is available
|
||||
cy.get('#pricingModal').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', () => {
|
||||
|
||||
@@ -53,7 +53,7 @@ describe('Feature discovery cloud', () => {
|
||||
|
||||
const testForTrialButton = () => {
|
||||
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 = () => {
|
||||
|
||||
@@ -693,7 +693,7 @@ const defaultServerConfig: AdminConfig = {
|
||||
WysiwygEditor: false,
|
||||
PeopleProduct: false,
|
||||
ReduceOnBoardingTaskList: false,
|
||||
OnboardingAutoShowLinkedBoard: true,
|
||||
OnboardingAutoShowLinkedBoard: false,
|
||||
ThreadsEverywhere: false,
|
||||
GlobalDrafts: true,
|
||||
OnboardingTourTips: true,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
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) => {
|
||||
// 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();
|
||||
|
||||
// Wait for Boards' bot image to be loaded
|
||||
await pw.shouldHaveFeatureFlag('OnboardingAutoShowLinkedBoard', true);
|
||||
const boardsWelcomePost = await channelsPage.getFirstPost();
|
||||
await expect(await boardsWelcomePost.getProfileImage('boards')).toBeVisible();
|
||||
await wait(duration.one_sec);
|
||||
// await pw.shouldHaveFeatureFlag('OnboardingAutoShowLinkedBoard', true);
|
||||
// const boardsWelcomePost = await channelsPage.getFirstPost();
|
||||
// await expect(await boardsWelcomePost.getProfileImage('boards')).toBeVisible();
|
||||
// await wait(duration.one_sec);
|
||||
|
||||
// Wait for Playbooks icon to be loaded in App bar, except in iphone
|
||||
if (!isSmallScreen(viewport)) {
|
||||
|
||||
@@ -7812,7 +7812,23 @@ func (c *Client4) GetChannelMemberCountsByGroup(channelID string, includeTimezon
|
||||
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
|
||||
// DEPRECATED - USE RequestTrialLicenseWithExtraFields (this method remains for backwards compatibility)
|
||||
func (c *Client4) RequestTrialLicense(users int) (*Response, error) {
|
||||
b, err := json.Marshal(map[string]any{"users": users, "terms_accepted": true})
|
||||
if err != nil {
|
||||
|
||||
@@ -51,9 +51,6 @@ type FeatureFlags struct {
|
||||
|
||||
CommandPalette bool
|
||||
|
||||
// Enable Boards as a product (multi-product architecture)
|
||||
BoardsProduct bool
|
||||
|
||||
// A/B Test on posting a welcome message
|
||||
SendWelcomePost bool
|
||||
|
||||
@@ -95,7 +92,6 @@ func (f *FeatureFlags) SetDefaults() {
|
||||
f.InsightsEnabled = true
|
||||
f.CommandPalette = false
|
||||
f.CallsEnabled = true
|
||||
f.BoardsProduct = false
|
||||
f.SendWelcomePost = true
|
||||
f.PostPriority = true
|
||||
f.PeopleProduct = false
|
||||
@@ -104,7 +100,7 @@ func (f *FeatureFlags) SetDefaults() {
|
||||
f.ThreadsEverywhere = false
|
||||
f.GlobalDrafts = true
|
||||
f.WysiwygEditor = false
|
||||
f.OnboardingAutoShowLinkedBoard = true
|
||||
f.OnboardingAutoShowLinkedBoard = false
|
||||
f.OnboardingTourTips = true
|
||||
}
|
||||
|
||||
|
||||
@@ -84,6 +84,48 @@ type TrialLicenseRequest struct {
|
||||
Users int `json:"users"`
|
||||
TermsAccepted bool `json:"terms_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 {
|
||||
|
||||
@@ -200,6 +200,100 @@ func TestLicenseRecordPreSave(t *testing.T) {
|
||||
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) {
|
||||
t.Run("detect trial license directly from the flag", func(t *testing.T) {
|
||||
license := &License{
|
||||
|
||||
@@ -118,9 +118,6 @@ var BuildDate string
|
||||
var BuildHash string
|
||||
var BuildHashEnterprise string
|
||||
var BuildEnterpriseReady string
|
||||
var BuildHashBoards string
|
||||
var BuildBoards string
|
||||
var BuildHashPlaybooks string
|
||||
var versionsWithoutHotFixes []string
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -86,15 +86,6 @@ else
|
||||
BUILD_CLIENT = false
|
||||
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
|
||||
# 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
|
||||
@@ -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.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.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_MINOR_VERSION = $(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f2)
|
||||
@@ -462,9 +450,9 @@ else
|
||||
endif
|
||||
|
||||
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_FEATUREFLAGS_BoardsProduct=true ./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=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_DISABLE_BOARDS=false ./scripts/test.sh "$(GO)" "-race $(GOFLAGS)" "$(BOARDS_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 ($(MM_NO_DOCKER),true)
|
||||
ifneq ($(TEMP_DOCKER_SERVICES),)
|
||||
@@ -475,9 +463,9 @@ ifneq ($(IS_CI),true)
|
||||
endif
|
||||
|
||||
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_FEATUREFLAGS_BoardsProduct=true ./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=true MM_DISABLE_BOARDS=true ./scripts/test.sh "$(GO)" "$(GOFLAGS)" "$(TE_PACKAGES) $(EE_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_DISABLE_BOARDS=true ./scripts/test.sh "$(GO)" "$(GOFLAGS)" "$(PLAYBOOKS_PACKAGES)" "$(TESTS)" "$(TESTFLAGS)" "$(GOBIN)" "45m" "count"
|
||||
ifneq ($(IS_CI),true)
|
||||
ifneq ($(MM_NO_DOCKER),true)
|
||||
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.
|
||||
@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.
|
||||
ifeq ($(BUILD_ENTERPRISE_READY),true)
|
||||
@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_FEATUREFLAGS_BoardsProduct=true $(GO) test $(GOFLAGS) -short $(BOARDS_PACKAGES)
|
||||
MM_DISABLE_PLAYBOOKS=false MM_FEATUREFLAGS_BoardsProduct=false $(GO) test $(GOFLAGS) -short $(PLAYBOOKS_PACKAGES)
|
||||
MM_DISABLE_PLAYBOOKS=true MM_DISABLE_BOARDS=true $(GO) test $(GOFLAGS) -short $(TE_PACKAGES) $(EE_PACKAGES)
|
||||
MM_DISABLE_PLAYBOOKS=true MM_DISABLE_BOARDS=false $(GO) test $(GOFLAGS) -short $(BOARDS_PACKAGES)
|
||||
MM_DISABLE_PLAYBOOKS=false MM_DISABLE_BOARDS=true $(GO) test $(GOFLAGS) -short $(PLAYBOOKS_PACKAGES)
|
||||
else
|
||||
@echo Running only TE tests
|
||||
MM_DISABLE_PLAYBOOKS=true MM_FEATUREFLAGS_BoardsProduct=false $(GO) test $(GOFLAGS) -short $(TE_PACKAGES)
|
||||
MM_DISABLE_PLAYBOOKS=true MM_FEATUREFLAGS_BoardsProduct=true $(GO) test $(GOFLAGS) -short $(BOARDS_PACKAGES)
|
||||
MM_DISABLE_PLAYBOOKS=false MM_FEATUREFLAGS_BoardsProduct=false $(GO) test $(GOFLAGS) -short $(PLAYBOOKS_PACKAGES)
|
||||
MM_DISABLE_PLAYBOOKS=true MM_DISABLE_BOARDS=true $(GO) test $(GOFLAGS) -short $(TE_PACKAGES)
|
||||
MM_DISABLE_PLAYBOOKS=true MM_DISABLE_BOARDS=false $(GO) test $(GOFLAGS) -short $(BOARDS_PACKAGES)
|
||||
MM_DISABLE_PLAYBOOKS=false MM_DISABLE_BOARDS=true $(GO) test $(GOFLAGS) -short $(PLAYBOOKS_PACKAGES)
|
||||
endif
|
||||
|
||||
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 {
|
||||
if !bp.configService.Config().FeatureFlags.BoardsProduct {
|
||||
bp.logger.Info("Boards product disabled via feature flag")
|
||||
return nil
|
||||
}
|
||||
|
||||
bp.logger.Info("Starting boards service")
|
||||
|
||||
adapter := newServiceAPIAdapter(bp)
|
||||
|
||||
@@ -80,12 +80,8 @@ func CreateBoardsConfig(mmconfig mm_model.Config, baseURL string, serverID strin
|
||||
showFullName = *mmconfig.PrivacySettings.ShowFullName
|
||||
}
|
||||
|
||||
serverRoot := baseURL + "/plugins/focalboard"
|
||||
if mmconfig.FeatureFlags.BoardsProduct {
|
||||
serverRoot = baseURL + "/boards"
|
||||
}
|
||||
return &config.Configuration{
|
||||
ServerRoot: serverRoot,
|
||||
ServerRoot: baseURL + "/boards",
|
||||
Port: -1,
|
||||
DBType: *mmconfig.SqlSettings.DriverName,
|
||||
DBConfigString: *mmconfig.SqlSettings.DataSource,
|
||||
|
||||
@@ -60,7 +60,21 @@ func (api *API) InitCloud() {
|
||||
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) {
|
||||
ensured := ensureCloudInterface(c, "Api4.getSubscription")
|
||||
if !ensured {
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.Channels().License().IsCloud() {
|
||||
c.Err = model.NewAppError("Api4.getSubscription", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
||||
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) {
|
||||
ensured := ensureCloudInterface(c, "Api4.changeSubscription")
|
||||
if !ensured {
|
||||
return
|
||||
}
|
||||
userId := c.AppContext.Session().UserId
|
||||
|
||||
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) {
|
||||
ensured := ensureCloudInterface(c, "Api4.requestCloudTrial")
|
||||
if !ensured {
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.Channels().License().IsCloud() {
|
||||
c.Err = model.NewAppError("Api4.requestCloudTrial", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
||||
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) {
|
||||
if !c.App.Channels().License().IsCloud() {
|
||||
c.Err = model.NewAppError("Api4.validateBusinessEmail", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionSysconsoleWriteBilling) {
|
||||
c.SetPermissionError(model.PermissionSysconsoleWriteBilling)
|
||||
ensured := ensureCloudInterface(c, "Api4.validateBusinessEmail")
|
||||
if !ensured {
|
||||
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) {
|
||||
ensured := ensureCloudInterface(c, "Api4.validateWorkspaceBusinessEmail")
|
||||
if !ensured {
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.Channels().License().IsCloud() {
|
||||
c.Err = model.NewAppError("Api4.validateWorkspaceBusinessEmail", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
||||
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) {
|
||||
ensured := ensureCloudInterface(c, "Api4.getSelfHostedProducts")
|
||||
if !ensured {
|
||||
return
|
||||
}
|
||||
|
||||
products, err := c.App.Cloud().GetSelfHostedProducts(c.AppContext.Session().UserId)
|
||||
if err != nil {
|
||||
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) {
|
||||
ensured := ensureCloudInterface(c, "Api4.getCloudProducts")
|
||||
if !ensured {
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.Channels().License().IsCloud() {
|
||||
c.Err = model.NewAppError("Api4.getCloudProducts", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
||||
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) {
|
||||
ensured := ensureCloudInterface(c, "Api4.getCloudLimits")
|
||||
if !ensured {
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.Channels().License().IsCloud() {
|
||||
c.Err = model.NewAppError("Api4.getCloudLimits", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
||||
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) {
|
||||
ensured := ensureCloudInterface(c, "Api4.getCloudCustomer")
|
||||
if !ensured {
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.Channels().License().IsCloud() {
|
||||
c.Err = model.NewAppError("Api4.getCloudCustomer", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
||||
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.
|
||||
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) {
|
||||
c.SetPermissionError(model.PermissionManageLicenseInformation)
|
||||
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) {
|
||||
ensured := ensureCloudInterface(c, "Api4.updateCloudCustomer")
|
||||
if !ensured {
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.Channels().License().IsCloud() {
|
||||
c.Err = model.NewAppError("Api4.updateCloudCustomer", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
||||
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) {
|
||||
ensured := ensureCloudInterface(c, "Api4.updateCloudCustomerAddress")
|
||||
if !ensured {
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.Channels().License().IsCloud() {
|
||||
c.Err = model.NewAppError("Api4.updateCloudCustomerAddress", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
||||
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) {
|
||||
ensured := ensureCloudInterface(c, "Api4.createCustomerPayment")
|
||||
if !ensured {
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.Channels().License().IsCloud() {
|
||||
c.Err = model.NewAppError("Api4.createCustomerPayment", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
||||
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) {
|
||||
ensured := ensureCloudInterface(c, "Api4.confirmCustomerPayment")
|
||||
if !ensured {
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.Channels().License().IsCloud() {
|
||||
c.Err = model.NewAppError("Api4.confirmCustomerPayment", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
||||
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) {
|
||||
ensured := ensureCloudInterface(c, "Api4.getInvoicesForSubscription")
|
||||
if !ensured {
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.Channels().License().IsCloud() {
|
||||
c.Err = model.NewAppError("Api4.getInvoicesForSubscription", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
||||
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) {
|
||||
ensured := ensureCloudInterface(c, "Api4.getSubscriptionInvoicePDF")
|
||||
if !ensured {
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.Channels().License().IsCloud() {
|
||||
c.Err = model.NewAppError("Api4.getSubscriptionInvoicePDF", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
||||
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) {
|
||||
ensured := ensureCloudInterface(c, "Api4.handleCWSWebhook")
|
||||
if !ensured {
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.Channels().License().IsCloud() {
|
||||
c.Err = model.NewAppError("Api4.handleCWSWebhook", "api.cloud.license_error", nil, "", http.StatusForbidden)
|
||||
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) {
|
||||
cloud := c.App.Cloud()
|
||||
if cloud == nil {
|
||||
c.Err = model.NewAppError("Api4.handleCWSHealthCheck", "api.server.cws.needs_enterprise_edition", nil, "", http.StatusBadRequest)
|
||||
ensured := ensureCloudInterface(c, "Api4.handleCheckCWSConnection")
|
||||
if !ensured {
|
||||
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)
|
||||
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) {
|
||||
ensured := ensureCloudInterface(c, "Api4.selfServeDeleteWorkspace")
|
||||
if !ensured {
|
||||
return
|
||||
}
|
||||
|
||||
bodyBytes, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
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()
|
||||
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.Client.Login(th.BasicUser.Email, th.BasicUser.Password)
|
||||
@@ -34,6 +43,15 @@ func Test_getCloudLimits(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
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.Client.Login(th.BasicUser.Email, th.BasicUser.Password)
|
||||
@@ -310,30 +328,6 @@ func Test_requestTrial(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) {
|
||||
th := Setup(t).InitBasic()
|
||||
|
||||
@@ -39,9 +39,8 @@ func (api *API) InitHostedCustomer() {
|
||||
}
|
||||
|
||||
func ensureSelfHostedAdmin(c *Context, where string) {
|
||||
cloud := c.App.Cloud()
|
||||
if cloud == nil {
|
||||
c.Err = model.NewAppError(where, "api.server.cws.needs_enterprise_edition", nil, "", http.StatusBadRequest)
|
||||
ensured := ensureCloudInterface(c, where)
|
||||
if !ensured {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -210,11 +210,7 @@ func requestTrialLicense(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
var trialRequest struct {
|
||||
Users int `json:"users"`
|
||||
TermsAccepted bool `json:"terms_accepted"`
|
||||
ReceiveEmailsAccepted bool `json:"receive_emails_accepted"`
|
||||
}
|
||||
var trialRequest *model.TrialLicenseRequest
|
||||
|
||||
b, readErr := io.ReadAll(r.Body)
|
||||
if readErr != nil {
|
||||
@@ -223,8 +219,16 @@ func requestTrialLicense(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
json.Unmarshal(b, &trialRequest)
|
||||
|
||||
if err := c.App.Channels().RequestTrialLicense(c.AppContext.Session().UserId, trialRequest.Users, trialRequest.TermsAccepted, trialRequest.ReceiveEmailsAccepted); err != nil {
|
||||
c.Err = err
|
||||
var appErr *model.AppError
|
||||
// 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
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
@@ -30,6 +30,7 @@ const ServerKey product.ServiceKey = "server"
|
||||
type licenseSvc interface {
|
||||
GetLicense() *model.License
|
||||
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.
|
||||
@@ -323,6 +324,10 @@ func (ch *Channels) License() *model.License {
|
||||
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 {
|
||||
return ch.licenseSvc.RequestTrialLicense(requesterID, users, termsAccepted,
|
||||
receiveEmailsAccepted)
|
||||
|
||||
@@ -36,6 +36,51 @@ func (w *licenseWrapper) GetLicense() *model.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 {
|
||||
if *w.srv.platform.Config().ExperimentalSettings.RestrictSystemAdmin {
|
||||
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 {
|
||||
// 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 {
|
||||
appErr, ok := err.(*model.AppError)
|
||||
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
|
||||
res, err = client.Do(request)
|
||||
if err != nil {
|
||||
mlog.Warn("error fetching OG image data", mlog.Err(err))
|
||||
}
|
||||
|
||||
if res != nil {
|
||||
body = res.Body
|
||||
|
||||
@@ -18,6 +18,10 @@ import (
|
||||
"testing"
|
||||
"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"
|
||||
ogimage "github.com/dyatlov/go-opengraph/opengraph/types/image"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -1311,6 +1315,49 @@ func TestGetImagesForPost(t *testing.T) {
|
||||
images := th.App.getImagesForPost(th.Context, post, []string{}, false)
|
||||
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) {
|
||||
|
||||
@@ -72,14 +72,14 @@ func (s *Server) initializeProducts(
|
||||
|
||||
func (s *Server) shouldStart(product string) bool {
|
||||
if product == "boards" {
|
||||
if !s.Config().FeatureFlags.BoardsProduct {
|
||||
s.Log().Warn("Skipping boards start: not enabled via feature flag")
|
||||
if os.Getenv("MM_DISABLE_BOARDS") == "true" {
|
||||
s.Log().Warn("Skipping Boards start: disabled via env var")
|
||||
return false
|
||||
}
|
||||
}
|
||||
if product == "playbooks" {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,6 +152,11 @@ var searchUserStoreTests = []searchTest{
|
||||
Fn: testSearchUsersInTeamUsernameWithUnderscore,
|
||||
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) {
|
||||
@@ -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 {
|
||||
return &model.UserSearchOptions{
|
||||
AllowFullNames: allowFullName,
|
||||
|
||||
@@ -22,6 +22,25 @@ func newSqlRemoteClusterStore(sqlStore *SqlStore) store.RemoteClusterStore {
|
||||
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) {
|
||||
remoteCluster.PreSave()
|
||||
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) {
|
||||
query := s.getQueryBuilder().
|
||||
Select("*").
|
||||
Select(remoteClusterFields("")...).
|
||||
From("RemoteClusters").
|
||||
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) {
|
||||
query := s.getQueryBuilder().
|
||||
Select("rc.*").
|
||||
Select(remoteClusterFields("rc")...).
|
||||
From("RemoteClusters rc")
|
||||
|
||||
if filter.InChannel != "" {
|
||||
|
||||
@@ -6,6 +6,7 @@ package sqlstore
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"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
|
||||
}
|
||||
|
||||
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.
|
||||
func (s SqlSharedChannelStore) Get(channelId string) (*model.SharedChannel, error) {
|
||||
var sc model.SharedChannel
|
||||
|
||||
query := s.getQueryBuilder().
|
||||
Select("*").
|
||||
Select(sharedChannelFields("")...).
|
||||
From("SharedChannels").
|
||||
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 {
|
||||
var selectStr string
|
||||
var selectFields []string
|
||||
if forCount {
|
||||
selectStr = "count(sc.ChannelId)"
|
||||
selectFields = []string{"count(sc.ChannelId)"}
|
||||
} else {
|
||||
selectStr = "sc.*"
|
||||
selectFields = sharedChannelFields("sc")
|
||||
}
|
||||
|
||||
query := s.getQueryBuilder().
|
||||
Select(selectStr).
|
||||
Select(selectFields...).
|
||||
From("SharedChannels AS sc")
|
||||
|
||||
if opts.MemberId != "" {
|
||||
@@ -364,12 +385,30 @@ func (s SqlSharedChannelStore) UpdateRemote(remote *model.SharedChannelRemote) (
|
||||
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.
|
||||
func (s SqlSharedChannelStore) GetRemote(id string) (*model.SharedChannelRemote, error) {
|
||||
var remote model.SharedChannelRemote
|
||||
|
||||
query := s.getQueryBuilder().
|
||||
Select("*").
|
||||
Select(sharedChannelRemoteFields("")...).
|
||||
From("SharedChannelRemotes").
|
||||
Where(sq.Eq{"SharedChannelRemotes.Id": id})
|
||||
|
||||
@@ -392,7 +431,7 @@ func (s SqlSharedChannelStore) GetRemoteByIds(channelId string, remoteId string)
|
||||
var remote model.SharedChannelRemote
|
||||
|
||||
query := s.getQueryBuilder().
|
||||
Select("*").
|
||||
Select(sharedChannelRemoteFields("")...).
|
||||
From("SharedChannelRemotes").
|
||||
Where(sq.Eq{"SharedChannelRemotes.ChannelId": channelId}).
|
||||
Where(sq.Eq{"SharedChannelRemotes.RemoteId": remoteId})
|
||||
@@ -416,7 +455,7 @@ func (s SqlSharedChannelStore) GetRemotes(opts model.SharedChannelRemoteFilterOp
|
||||
remotes := []*model.SharedChannelRemote{}
|
||||
|
||||
query := s.getQueryBuilder().
|
||||
Select("*").
|
||||
Select(sharedChannelRemoteFields("")...).
|
||||
From("SharedChannelRemotes")
|
||||
|
||||
if opts.ChannelId != "" {
|
||||
@@ -570,6 +609,20 @@ func (s SqlSharedChannelStore) GetRemotesStatus(channelId string) ([]*model.Shar
|
||||
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.
|
||||
func (s SqlSharedChannelStore) SaveUser(scUser *model.SharedChannelUser) (*model.SharedChannelUser, error) {
|
||||
scUser.PreSave()
|
||||
@@ -578,7 +631,7 @@ func (s SqlSharedChannelStore) SaveUser(scUser *model.SharedChannelUser) (*model
|
||||
}
|
||||
|
||||
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).
|
||||
ToSql()
|
||||
if err != nil {
|
||||
@@ -595,7 +648,7 @@ func (s SqlSharedChannelStore) GetSingleUser(userID string, channelID string, re
|
||||
var scu model.SharedChannelUser
|
||||
|
||||
squery, args, err := s.getQueryBuilder().
|
||||
Select("*").
|
||||
Select(sharedChannelUserFields("")...).
|
||||
From("SharedChannelUsers").
|
||||
Where(sq.Eq{"SharedChannelUsers.UserId": userID}).
|
||||
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.
|
||||
func (s SqlSharedChannelStore) GetUsersForUser(userID string) ([]*model.SharedChannelUser, error) {
|
||||
squery, args, err := s.getQueryBuilder().
|
||||
Select("*").
|
||||
Select(sharedChannelUserFields("")...).
|
||||
From("SharedChannelUsers").
|
||||
Where(sq.Eq{"SharedChannelUsers.UserId": userID}).
|
||||
ToSql()
|
||||
@@ -645,7 +698,9 @@ func (s SqlSharedChannelStore) GetUsersForSync(filter model.GetUsersForSyncFilte
|
||||
}
|
||||
|
||||
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().
|
||||
From("Users AS u").
|
||||
Join("SharedChannelUsers AS scu ON u.Id = scu.UserId").
|
||||
@@ -723,6 +778,19 @@ func (s SqlSharedChannelStore) UpdateUserLastSyncAt(userID string, channelID str
|
||||
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.
|
||||
func (s SqlSharedChannelStore) SaveAttachment(attachment *model.SharedChannelAttachment) (*model.SharedChannelAttachment, error) {
|
||||
attachment.PreSave()
|
||||
@@ -731,7 +799,7 @@ func (s SqlSharedChannelStore) SaveAttachment(attachment *model.SharedChannelAtt
|
||||
}
|
||||
|
||||
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).
|
||||
ToSql()
|
||||
if err != nil {
|
||||
@@ -753,7 +821,7 @@ func (s SqlSharedChannelStore) UpsertAttachment(attachment *model.SharedChannelA
|
||||
}
|
||||
query := s.getQueryBuilder().
|
||||
Insert("SharedChannelAttachments").
|
||||
Columns("Id", "FileId", "RemoteId", "CreateAt", "LastSyncAt").
|
||||
Columns(sharedChannelAttachementFields("")...).
|
||||
Values(attachment.Id, attachment.FileId, attachment.RemoteId, attachment.CreateAt, attachment.LastSyncAt)
|
||||
|
||||
if s.DriverName() == model.DatabaseDriverMysql {
|
||||
@@ -777,7 +845,7 @@ func (s SqlSharedChannelStore) GetAttachment(fileId string, remoteId string) (*m
|
||||
var attachment model.SharedChannelAttachment
|
||||
|
||||
squery, args, err := s.getQueryBuilder().
|
||||
Select("*").
|
||||
Select(sharedChannelAttachementFields("")...).
|
||||
From("SharedChannelAttachments").
|
||||
Where(sq.Eq{"SharedChannelAttachments.FileId": fileId}).
|
||||
Where(sq.Eq{"SharedChannelAttachments.RemoteId": remoteId}).
|
||||
|
||||
@@ -1540,7 +1540,7 @@ func generateSearchQuery(query sq.SelectBuilder, terms []string, fields []string
|
||||
} else {
|
||||
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...)
|
||||
}
|
||||
|
||||
@@ -223,9 +223,6 @@ func GenerateLimitedClientConfig(c *model.Config, telemetryID string, license *m
|
||||
props["BuildHash"] = model.BuildHash
|
||||
props["BuildHashEnterprise"] = model.BuildHashEnterprise
|
||||
props["BuildEnterpriseReady"] = model.BuildEnterpriseReady
|
||||
props["BuildHashBoards"] = model.BuildHashBoards
|
||||
props["BuildBoards"] = model.BuildBoards
|
||||
props["BuildHashPlaybooks"] = model.BuildHashPlaybooks
|
||||
|
||||
props["EnableBotAccountCreation"] = strconv.FormatBool(*c.ServiceSettings.EnableBotAccountCreation)
|
||||
props["EnableFile"] = strconv.FormatBool(*c.LogSettings.EnableFile)
|
||||
|
||||
@@ -109,7 +109,6 @@ services:
|
||||
- "RUN_SERVER_IN_BACKGROUND=false"
|
||||
- "MM_CLUSTERSETTINGS_ENABLE=true"
|
||||
- "MM_CLUSTERSETTINGS_CLUSTERNAME=mm_dev_cluster"
|
||||
- "MM_FEATUREFLAGS_BoardsProduct=true"
|
||||
networks:
|
||||
- mm-test
|
||||
depends_on:
|
||||
@@ -147,7 +146,6 @@ services:
|
||||
- "RUN_SERVER_IN_BACKGROUND=false"
|
||||
- "MM_CLUSTERSETTINGS_ENABLE=true"
|
||||
- "MM_CLUSTERSETTINGS_CLUSTERNAME=mm_dev_cluster"
|
||||
- "MM_FEATUREFLAGS_BoardsProduct=true"
|
||||
networks:
|
||||
- mm-test
|
||||
depends_on:
|
||||
@@ -185,7 +183,6 @@ services:
|
||||
- "RUN_SERVER_IN_BACKGROUND=false"
|
||||
- "MM_CLUSTERSETTINGS_ENABLE=true"
|
||||
- "MM_CLUSTERSETTINGS_CLUSTERNAME=mm_dev_cluster"
|
||||
- "MM_FEATUREFLAGS_BoardsProduct=true"
|
||||
networks:
|
||||
- mm-test
|
||||
depends_on:
|
||||
|
||||
@@ -2049,6 +2049,10 @@
|
||||
"id": "api.license.request-trial.bad-request",
|
||||
"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",
|
||||
"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.")
|
||||
diagnosticID := pp.serviceAdapter.GetDiagnosticID()
|
||||
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 {
|
||||
return errors.Wrapf(err, "failed init telemetry client")
|
||||
}
|
||||
@@ -569,7 +569,7 @@ func (pp *playbooksProduct) Stop() error {
|
||||
func newMetricsInstance() *metrics.Metrics {
|
||||
// Init metrics
|
||||
instanceInfo := metrics.InstanceInfo{
|
||||
Version: model.BuildHashPlaybooks,
|
||||
Version: model.BuildHash,
|
||||
InstallationID: os.Getenv("MM_CLOUD_INSTALLATION_ID"),
|
||||
}
|
||||
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
|
||||
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.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")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
save-exact=true
|
||||
legacy-peer-deps=true
|
||||
global-style=true
|
||||
@@ -89,7 +89,7 @@
|
||||
"unused-imports/no-unused-imports": 2,
|
||||
"no-relative-import-paths/no-relative-import-paths": [
|
||||
"error",
|
||||
{ "allowSameFolder": true, "rootDir": "webapp/src"}
|
||||
{ "allowSameFolder": true, "rootDir": "webapp/boards"}
|
||||
],
|
||||
/* "no-restricted-imports": ["error", {
|
||||
"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",
|
||||
"AttachmentBlock.DeleteAction": "delete",
|
||||
"AttachmentBlock.addElement": "add {type}",
|
||||
@@ -115,7 +117,6 @@
|
||||
"CenterPanel.Login": "Login",
|
||||
"CenterPanel.Share": "Share",
|
||||
"ChannelIntro.CreateBoard": "Create a board",
|
||||
"CloudMessage.cloud-server": "Get your own free cloud server.",
|
||||
"ColorOption.selectColor": "Select {color} Color",
|
||||
"Comment.delete": "Delete",
|
||||
"CommentsList.send": "Send",
|
||||
@@ -138,6 +139,7 @@
|
||||
"ContentBlock.moveDown": "Move down",
|
||||
"ContentBlock.moveUp": "Move up",
|
||||
"ContentBlock.text": "text",
|
||||
"DateFilter.empty": "Empty",
|
||||
"DateRange.clear": "Clear",
|
||||
"DateRange.empty": "Empty",
|
||||
"DateRange.endDate": "End date",
|
||||
@@ -156,10 +158,14 @@
|
||||
"Filter.ends-with": "ends with",
|
||||
"Filter.includes": "includes",
|
||||
"Filter.is": "is",
|
||||
"Filter.is-after": "is after",
|
||||
"Filter.is-before": "is before",
|
||||
"Filter.is-empty": "is empty",
|
||||
"Filter.is-not-empty": "is not empty",
|
||||
"Filter.is-not-set": "is not set",
|
||||
"Filter.is-set": "is set",
|
||||
"Filter.isafter": "is after",
|
||||
"Filter.isbefore": "is before",
|
||||
"Filter.not-contains": "doesn't contain",
|
||||
"Filter.not-ends-with": "doesn't end with",
|
||||
"Filter.not-includes": "doesn't include",
|
||||
@@ -243,16 +249,11 @@
|
||||
"ShareBoard.userPermissionsYouText": "(You)",
|
||||
"ShareTemplate.Title": "Share template",
|
||||
"ShareTemplate.searchPlaceholder": "Search for people",
|
||||
"Sidebar.about": "About Focalboard",
|
||||
"Sidebar.add-board": "+ Add board",
|
||||
"Sidebar.changePassword": "Change password",
|
||||
"Sidebar.delete-board": "Delete board",
|
||||
"Sidebar.duplicate-board": "Duplicate board",
|
||||
"Sidebar.export-archive": "Export archive",
|
||||
"Sidebar.import": "Import",
|
||||
"Sidebar.import-archive": "Import archive",
|
||||
"Sidebar.invite-users": "Invite users",
|
||||
"Sidebar.logout": "Log out",
|
||||
"Sidebar.new-category.badge": "New",
|
||||
"Sidebar.new-category.drag-boards-cta": "Drag boards here...",
|
||||
"Sidebar.no-boards-in-category": "No boards inside",
|
||||
@@ -306,6 +307,7 @@
|
||||
"ValueSelector.valueSelector": "Value selector",
|
||||
"ValueSelectorLabel.openMenu": "Open menu",
|
||||
"VersionMessage.help": "Check out what's new in this version.",
|
||||
"VersionMessage.learn-more": "Learn more",
|
||||
"View.AddView": "Add view",
|
||||
"View.Board": "Board",
|
||||
"View.DeleteView": "Delete view",
|
||||
@@ -360,6 +362,9 @@
|
||||
"WelcomePage.StartUsingIt.Text": "Start using it",
|
||||
"Workspace.editing-board-template": "You're editing a board template.",
|
||||
"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-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.",
|
||||
@@ -374,7 +379,6 @@
|
||||
"calendar.week": "Week",
|
||||
"centerPanel.undefined": "No {propertyName}",
|
||||
"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.",
|
||||
"default-properties.badges": "Comments and description",
|
||||
"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": "npm run check-lint && npm run check-style",
|
||||
"fix": "npm run check-lint:fix && npm run check-style:fix",
|
||||
"test": "jest --forceExit --detectOpenHandles --verbose",
|
||||
"test:watch": "jest --watch",
|
||||
"test:updatesnapshot": "jest --updateSnapshot",
|
||||
"test-ci": "jest --ci --forceExit --detectOpenHandles --maxWorkers=100%",
|
||||
"test": "cross-env TZ=Etc/UTC jest",
|
||||
"test:watch": "cross-env TZ=Etc/UTC jest --watch",
|
||||
"test:updatesnapshot": "cross-env TZ=Etc/UTC jest --updateSnapshot",
|
||||
"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"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -44,78 +45,26 @@
|
||||
"fullcalendar": "^5.10.2",
|
||||
"glob-parent": "6.0.2",
|
||||
"lodash": "^4.17.21",
|
||||
"marked": "^4.0.12",
|
||||
"mattermost-redux": "5.33.1",
|
||||
"marked": "4.0.17",
|
||||
"mini-create-react-context": "^0.4.1",
|
||||
"moment": "^2.29.1",
|
||||
"nanoevents": "^5.1.13",
|
||||
"react": "^16.13.0",
|
||||
"react": "17.0.2",
|
||||
"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-html5-backend": "^14.0.0",
|
||||
"react-dnd-scrolling": "^1.2.1",
|
||||
"react-dnd-touch-backend": "^14.0.0",
|
||||
"react-dom": "^16.13.0",
|
||||
"react-dom": "17.0.2",
|
||||
"react-hot-keys": "^2.7.1",
|
||||
"react-hotkeys-hook": "^3.4.4",
|
||||
"react-intl": "^5.20.0",
|
||||
"react-redux": "^7.2.1",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-select": "5.7.0",
|
||||
"react-select": "5.5.9",
|
||||
"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": {
|
||||
"@babel/cli": "7.17.6",
|
||||
"@babel/core": "7.17.8",
|
||||
@@ -130,30 +79,32 @@
|
||||
"@babel/runtime": "7.17.8",
|
||||
"@formatjs/cli": "^4.8.2",
|
||||
"@formatjs/ts-transformer": "^3.9.2",
|
||||
"@swc/jest": "^0.2.24",
|
||||
"@testing-library/dom": "^8.11.4",
|
||||
"@testing-library/jest-dom": "^5.16.3",
|
||||
"@testing-library/react": "^11.2.5",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@swc/core": "1.3.40",
|
||||
"@swc/jest": "0.2.24",
|
||||
"@testing-library/dom": "8.20.0",
|
||||
"@testing-library/jest-dom": "5.16.5",
|
||||
"@testing-library/react": "12.1.5",
|
||||
"@testing-library/user-event": "14.4.3",
|
||||
"@types/color": "^3.0.3",
|
||||
"@types/draft-js": "^0.11.9",
|
||||
"@types/emoji-mart": "^3.0.9",
|
||||
"@types/enzyme": "3.10.11",
|
||||
"@types/jest": "27.4.1",
|
||||
"@types/jest": "29.4.2",
|
||||
"@types/lodash": "4.14.182",
|
||||
"@types/marked": "^4.0.3",
|
||||
"@types/nanoevents": "^1.0.0",
|
||||
"@types/node": "17.0.23",
|
||||
"@types/react": "^17.0.43",
|
||||
"@types/node": "16.11.7",
|
||||
"@types/react": "17.0.53",
|
||||
"@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-router-dom": "^5.3.3",
|
||||
"@types/react-transition-group": "4.4.4",
|
||||
"@types/redux-mock-store": "1.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "5.16.0",
|
||||
"@typescript-eslint/parser": "5.16.0",
|
||||
"babel-eslint": "10.1.0",
|
||||
"babel-eslint": "10.1.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "6.7.1",
|
||||
"eslint": "^8.11.0",
|
||||
@@ -168,7 +119,7 @@
|
||||
"eslint-plugin-react": "7.29.4",
|
||||
"eslint-plugin-react-hooks": "4.3.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",
|
||||
"image-webpack-loader": "8.1.0",
|
||||
"imagemin-gifsicle": "^7.0.0",
|
||||
@@ -178,10 +129,12 @@
|
||||
"imagemin-svgo": "^10.0.1",
|
||||
"imagemin-webp": "7.0.0",
|
||||
"isomorphic-fetch": "3.0.0",
|
||||
"jest": "27.5.1",
|
||||
"jest-canvas-mock": "2.3.1",
|
||||
"jest-junit": "13.0.0",
|
||||
"jest-mock": "27.5.1",
|
||||
"jest": "29.5.0",
|
||||
"jest-canvas-mock": "2.4.0",
|
||||
"jest-environment-jsdom": "29.5.0",
|
||||
"jest-fail-on-console": "3.0.2",
|
||||
"jest-junit": "15.0.0",
|
||||
"jest-mock": "29.4.3",
|
||||
"prettier": "^2.6.1",
|
||||
"redux-mock-store": "^1.5.4",
|
||||
"sass": "1.49.9",
|
||||
|
||||
@@ -1,46 +1,46 @@
|
||||
// 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`] = `
|
||||
Array [
|
||||
Object {
|
||||
"deletedFields": Array [],
|
||||
"updatedFields": Object {
|
||||
[
|
||||
{
|
||||
"deletedFields": [],
|
||||
"updatedFields": {
|
||||
"newField": "new field",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"deletedFields": Array [
|
||||
{
|
||||
"deletedFields": [
|
||||
"newField",
|
||||
],
|
||||
"updatedFields": Object {},
|
||||
"updatedFields": {},
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`block tests correctly generate patches from two blocks should generate two empty patches for the same block 1`] = `
|
||||
Array [
|
||||
Object {
|
||||
"deletedFields": Array [],
|
||||
"updatedFields": Object {},
|
||||
[
|
||||
{
|
||||
"deletedFields": [],
|
||||
"updatedFields": {},
|
||||
},
|
||||
Object {
|
||||
"deletedFields": Array [],
|
||||
"updatedFields": Object {},
|
||||
{
|
||||
"deletedFields": [],
|
||||
"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`] = `
|
||||
Array [
|
||||
Object {
|
||||
"deletedFields": Array [
|
||||
[
|
||||
{
|
||||
"deletedFields": [
|
||||
"test",
|
||||
],
|
||||
"updatedFields": Object {},
|
||||
"updatedFields": {},
|
||||
},
|
||||
Object {
|
||||
"deletedFields": Array [],
|
||||
"updatedFields": Object {
|
||||
{
|
||||
"deletedFields": [],
|
||||
"updatedFields": {
|
||||
"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`] = `
|
||||
Array [
|
||||
Object {
|
||||
"deletedFields": Array [],
|
||||
[
|
||||
{
|
||||
"deletedFields": [],
|
||||
"parentId": "new-parent-id",
|
||||
"updatedFields": Object {},
|
||||
"updatedFields": {},
|
||||
},
|
||||
Object {
|
||||
"deletedFields": Array [],
|
||||
{
|
||||
"deletedFields": [],
|
||||
"parentId": "old-parent-id",
|
||||
"updatedFields": Object {},
|
||||
"updatedFields": {},
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
@@ -1,52 +1,52 @@
|
||||
// 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`] = `
|
||||
Array [
|
||||
Object {
|
||||
"blockIDs": Array [
|
||||
[
|
||||
{
|
||||
"blockIDs": [
|
||||
"test-old-block-id",
|
||||
],
|
||||
"blockPatches": Array [
|
||||
Object {
|
||||
"deletedFields": Array [],
|
||||
"updatedFields": Object {
|
||||
"blockPatches": [
|
||||
{
|
||||
"deletedFields": [],
|
||||
"updatedFields": {
|
||||
"newField": "new field",
|
||||
},
|
||||
},
|
||||
],
|
||||
"boardIDs": Array [
|
||||
"boardIDs": [
|
||||
"test-board-id",
|
||||
],
|
||||
"boardPatches": Array [
|
||||
Object {
|
||||
"deletedCardProperties": Array [],
|
||||
"deletedProperties": Array [],
|
||||
"updatedCardProperties": Array [],
|
||||
"updatedProperties": Object {},
|
||||
"boardPatches": [
|
||||
{
|
||||
"deletedCardProperties": [],
|
||||
"deletedProperties": [],
|
||||
"updatedCardProperties": [],
|
||||
"updatedProperties": {},
|
||||
},
|
||||
],
|
||||
},
|
||||
Object {
|
||||
"blockIDs": Array [
|
||||
{
|
||||
"blockIDs": [
|
||||
"test-old-block-id",
|
||||
],
|
||||
"blockPatches": Array [
|
||||
Object {
|
||||
"deletedFields": Array [
|
||||
"blockPatches": [
|
||||
{
|
||||
"deletedFields": [
|
||||
"newField",
|
||||
],
|
||||
"updatedFields": Object {},
|
||||
"updatedFields": {},
|
||||
},
|
||||
],
|
||||
"boardIDs": Array [
|
||||
"boardIDs": [
|
||||
"test-board-id",
|
||||
],
|
||||
"boardPatches": Array [
|
||||
Object {
|
||||
"deletedCardProperties": Array [],
|
||||
"deletedProperties": Array [],
|
||||
"updatedCardProperties": Array [],
|
||||
"updatedProperties": Object {},
|
||||
"boardPatches": [
|
||||
{
|
||||
"deletedCardProperties": [],
|
||||
"deletedProperties": [],
|
||||
"updatedCardProperties": [],
|
||||
"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`] = `
|
||||
Array [
|
||||
Object {
|
||||
"blockIDs": Array [
|
||||
[
|
||||
{
|
||||
"blockIDs": [
|
||||
"test-card-id",
|
||||
],
|
||||
"blockPatches": Array [
|
||||
Object {
|
||||
"deletedFields": Array [],
|
||||
"updatedFields": Object {},
|
||||
"blockPatches": [
|
||||
{
|
||||
"deletedFields": [],
|
||||
"updatedFields": {},
|
||||
},
|
||||
],
|
||||
"boardIDs": Array [
|
||||
"boardIDs": [
|
||||
"test-board-id",
|
||||
],
|
||||
"boardPatches": Array [
|
||||
Object {
|
||||
"deletedCardProperties": Array [],
|
||||
"deletedProperties": Array [],
|
||||
"updatedCardProperties": Array [],
|
||||
"updatedProperties": Object {},
|
||||
"boardPatches": [
|
||||
{
|
||||
"deletedCardProperties": [],
|
||||
"deletedProperties": [],
|
||||
"updatedCardProperties": [],
|
||||
"updatedProperties": {},
|
||||
},
|
||||
],
|
||||
},
|
||||
Object {
|
||||
"blockIDs": Array [
|
||||
{
|
||||
"blockIDs": [
|
||||
"test-card-id",
|
||||
],
|
||||
"blockPatches": Array [
|
||||
Object {
|
||||
"deletedFields": Array [],
|
||||
"updatedFields": Object {},
|
||||
"blockPatches": [
|
||||
{
|
||||
"deletedFields": [],
|
||||
"updatedFields": {},
|
||||
},
|
||||
],
|
||||
"boardIDs": Array [
|
||||
"boardIDs": [
|
||||
"test-board-id",
|
||||
],
|
||||
"boardPatches": Array [
|
||||
Object {
|
||||
"deletedCardProperties": Array [],
|
||||
"deletedProperties": Array [],
|
||||
"updatedCardProperties": Array [],
|
||||
"updatedProperties": Object {},
|
||||
"boardPatches": [
|
||||
{
|
||||
"deletedCardProperties": [],
|
||||
"deletedProperties": [],
|
||||
"updatedCardProperties": [],
|
||||
"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`] = `
|
||||
Array [
|
||||
Object {
|
||||
"deletedCardProperties": Array [],
|
||||
"deletedProperties": Array [],
|
||||
"updatedCardProperties": Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"deletedCardProperties": [],
|
||||
"deletedProperties": [],
|
||||
"updatedCardProperties": [
|
||||
{
|
||||
"id": "new-property-id",
|
||||
"name": "property-name",
|
||||
"options": Array [
|
||||
Object {
|
||||
"options": [
|
||||
{
|
||||
"color": "propColorYellow",
|
||||
"id": "opt",
|
||||
"value": "val",
|
||||
@@ -121,30 +121,30 @@ Array [
|
||||
"type": "select",
|
||||
},
|
||||
],
|
||||
"updatedProperties": Object {},
|
||||
"updatedProperties": {},
|
||||
},
|
||||
Object {
|
||||
"deletedCardProperties": Array [
|
||||
{
|
||||
"deletedCardProperties": [
|
||||
"new-property-id",
|
||||
],
|
||||
"deletedProperties": Array [],
|
||||
"updatedCardProperties": Array [],
|
||||
"updatedProperties": Object {},
|
||||
"deletedProperties": [],
|
||||
"updatedCardProperties": [],
|
||||
"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`] = `
|
||||
Array [
|
||||
Object {
|
||||
"deletedCardProperties": Array [],
|
||||
"deletedProperties": Array [],
|
||||
"updatedCardProperties": Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"deletedCardProperties": [],
|
||||
"deletedProperties": [],
|
||||
"updatedCardProperties": [
|
||||
{
|
||||
"id": "new-property-id",
|
||||
"name": "property-name",
|
||||
"options": Array [
|
||||
Object {
|
||||
"options": [
|
||||
{
|
||||
"color": "propColorYellow",
|
||||
"id": "opt",
|
||||
"value": "val",
|
||||
@@ -153,17 +153,17 @@ Array [
|
||||
"type": "select",
|
||||
},
|
||||
],
|
||||
"updatedProperties": Object {},
|
||||
"updatedProperties": {},
|
||||
},
|
||||
Object {
|
||||
"deletedCardProperties": Array [],
|
||||
"deletedProperties": Array [],
|
||||
"updatedCardProperties": Array [
|
||||
Object {
|
||||
{
|
||||
"deletedCardProperties": [],
|
||||
"deletedProperties": [],
|
||||
"updatedCardProperties": [
|
||||
{
|
||||
"id": "new-property-id",
|
||||
"name": "a-different-name",
|
||||
"options": Array [
|
||||
Object {
|
||||
"options": [
|
||||
{
|
||||
"color": "propColorYellow",
|
||||
"id": "opt",
|
||||
"value": "val",
|
||||
@@ -172,22 +172,22 @@ Array [
|
||||
"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`] = `
|
||||
Array [
|
||||
Object {
|
||||
"deletedCardProperties": Array [],
|
||||
"deletedProperties": Array [],
|
||||
"updatedCardProperties": Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"deletedCardProperties": [],
|
||||
"deletedProperties": [],
|
||||
"updatedCardProperties": [
|
||||
{
|
||||
"id": "new-property-id",
|
||||
"name": "property-name",
|
||||
"options": Array [
|
||||
Object {
|
||||
"options": [
|
||||
{
|
||||
"color": "propColorYellow",
|
||||
"id": "opt",
|
||||
"value": "val",
|
||||
@@ -196,17 +196,17 @@ Array [
|
||||
"type": "select",
|
||||
},
|
||||
],
|
||||
"updatedProperties": Object {},
|
||||
"updatedProperties": {},
|
||||
},
|
||||
Object {
|
||||
"deletedCardProperties": Array [],
|
||||
"deletedProperties": Array [],
|
||||
"updatedCardProperties": Array [
|
||||
Object {
|
||||
{
|
||||
"deletedCardProperties": [],
|
||||
"deletedProperties": [],
|
||||
"updatedCardProperties": [
|
||||
{
|
||||
"id": "new-property-id",
|
||||
"name": "property-name",
|
||||
"options": Array [
|
||||
Object {
|
||||
"options": [
|
||||
{
|
||||
"color": "propColorBrown",
|
||||
"id": "another-opt",
|
||||
"value": "val",
|
||||
@@ -215,45 +215,45 @@ Array [
|
||||
"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`] = `
|
||||
Array [
|
||||
Object {
|
||||
"deletedCardProperties": Array [],
|
||||
"deletedProperties": Array [],
|
||||
"updatedCardProperties": Array [],
|
||||
"updatedProperties": Object {
|
||||
[
|
||||
{
|
||||
"deletedCardProperties": [],
|
||||
"deletedProperties": [],
|
||||
"updatedCardProperties": [],
|
||||
"updatedProperties": {
|
||||
"prop1": "val1",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"deletedCardProperties": Array [],
|
||||
"deletedProperties": Array [
|
||||
{
|
||||
"deletedCardProperties": [],
|
||||
"deletedProperties": [
|
||||
"prop1",
|
||||
],
|
||||
"updatedCardProperties": Array [],
|
||||
"updatedProperties": Object {},
|
||||
"updatedCardProperties": [],
|
||||
"updatedProperties": {},
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`board tests correctly generate patches from two boards should generate two empty patches for the same board 1`] = `
|
||||
Array [
|
||||
Object {
|
||||
"deletedCardProperties": Array [],
|
||||
"deletedProperties": Array [],
|
||||
"updatedCardProperties": Array [],
|
||||
"updatedProperties": Object {},
|
||||
[
|
||||
{
|
||||
"deletedCardProperties": [],
|
||||
"deletedProperties": [],
|
||||
"updatedCardProperties": [],
|
||||
"updatedProperties": {},
|
||||
},
|
||||
Object {
|
||||
"deletedCardProperties": Array [],
|
||||
"deletedProperties": Array [],
|
||||
"updatedCardProperties": Array [],
|
||||
"updatedProperties": Object {},
|
||||
{
|
||||
"deletedCardProperties": [],
|
||||
"deletedProperties": [],
|
||||
"updatedCardProperties": [],
|
||||
"updatedProperties": {},
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
@@ -12,7 +12,7 @@ import {Utils} from './utils'
|
||||
import {IPropertyTemplate} from './blocks/board'
|
||||
|
||||
jest.mock('./utils')
|
||||
const mockedUtils = mocked(Utils, true)
|
||||
const mockedUtils = mocked(Utils)
|
||||
|
||||
const dayMillis = 24 * 60 * 60 * 1000
|
||||
|
||||
|
||||
@@ -434,7 +434,7 @@ exports[`components/cardDialog limited card shows hidden view (no toolbar) 1`] =
|
||||
<p
|
||||
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 />
|
||||
<a
|
||||
class="CardDetail__limited-link"
|
||||
|
||||
@@ -317,7 +317,7 @@ exports[`components/centerPanel Clicking on the Hidden card count should open a
|
||||
</button>
|
||||
<span
|
||||
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
|
||||
</span>
|
||||
@@ -982,7 +982,7 @@ exports[`components/centerPanel return centerPanel and click on card to show car
|
||||
>
|
||||
<span
|
||||
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
|
||||
</span>
|
||||
@@ -1580,7 +1580,7 @@ exports[`components/centerPanel return centerPanel and click on new card to edit
|
||||
</button>
|
||||
<span
|
||||
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
|
||||
</span>
|
||||
@@ -2102,7 +2102,7 @@ exports[`components/centerPanel return centerPanel and press touch 1 with readon
|
||||
</button>
|
||||
<span
|
||||
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
|
||||
</span>
|
||||
@@ -2639,7 +2639,7 @@ exports[`components/centerPanel return centerPanel and press touch ctrl+d for on
|
||||
</button>
|
||||
<span
|
||||
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
|
||||
</span>
|
||||
@@ -3298,7 +3298,7 @@ exports[`components/centerPanel return centerPanel and press touch del for one c
|
||||
</button>
|
||||
<span
|
||||
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
|
||||
</span>
|
||||
@@ -3957,7 +3957,7 @@ exports[`components/centerPanel return centerPanel and press touch esc for one c
|
||||
</button>
|
||||
<span
|
||||
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
|
||||
</span>
|
||||
@@ -4616,7 +4616,7 @@ exports[`components/centerPanel return centerPanel and press touch esc for one c
|
||||
</button>
|
||||
<span
|
||||
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
|
||||
</span>
|
||||
@@ -5275,7 +5275,7 @@ exports[`components/centerPanel return centerPanel and press touch esc for two c
|
||||
</button>
|
||||
<span
|
||||
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
|
||||
</span>
|
||||
@@ -5934,7 +5934,7 @@ exports[`components/centerPanel return centerPanel and press touch esc for two c
|
||||
</button>
|
||||
<span
|
||||
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
|
||||
</span>
|
||||
@@ -6593,7 +6593,7 @@ exports[`components/centerPanel return centerPanel and press touch esc for two c
|
||||
</button>
|
||||
<span
|
||||
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
|
||||
</span>
|
||||
@@ -7252,7 +7252,7 @@ exports[`components/centerPanel return centerPanel and select one card and click
|
||||
</button>
|
||||
<span
|
||||
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
|
||||
</span>
|
||||
@@ -7911,7 +7911,7 @@ exports[`components/centerPanel return centerPanel and select one card and click
|
||||
</button>
|
||||
<span
|
||||
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
|
||||
</span>
|
||||
@@ -8802,7 +8802,7 @@ exports[`components/centerPanel should match snapshot for Kanban 1`] = `
|
||||
>
|
||||
<span
|
||||
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
|
||||
</span>
|
||||
@@ -9322,7 +9322,7 @@ exports[`components/centerPanel should match snapshot for Kanban, not shared 1`]
|
||||
>
|
||||
<span
|
||||
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
|
||||
</span>
|
||||
@@ -9886,7 +9886,7 @@ exports[`components/centerPanel should match snapshot for Table 1`] = `
|
||||
</button>
|
||||
<span
|
||||
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
|
||||
</span>
|
||||
|
||||
@@ -54,7 +54,7 @@ exports[`/components/confirmAddUserForNotifications should match snapshot 1`] =
|
||||
class="ConfirmAddUserForNotifications"
|
||||
>
|
||||
<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>
|
||||
Do you want to add fake-username to the board?
|
||||
|
||||
@@ -1041,7 +1041,7 @@ exports[`properties/person show multiple, display modal 2`] = `
|
||||
<span
|
||||
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>
|
||||
<div
|
||||
|
||||
@@ -16,36 +16,15 @@ exports[`components/propertyValueElement Generic fields should allow cancel 1`]
|
||||
exports[`components/propertyValueElement URL fields should allow cancel 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="URLProperty octo-propertyvalue"
|
||||
class="URLProperty"
|
||||
>
|
||||
<a
|
||||
class="link"
|
||||
href="http://localhost"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
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>
|
||||
<input
|
||||
class="Editable octo-propertyvalue"
|
||||
placeholder="Empty"
|
||||
style="width: 100%;"
|
||||
title=""
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -110,7 +110,7 @@ exports[`components/rhsChannelBoardItem render board with menu open 1`] = `
|
||||
<div
|
||||
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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`/components/viewMenu should match snapshot 1`] = `
|
||||
Object {
|
||||
{
|
||||
"asFragment": [Function],
|
||||
"baseElement": <body>
|
||||
<div>
|
||||
@@ -530,7 +530,7 @@ Object {
|
||||
`;
|
||||
|
||||
exports[`/components/viewMenu should match snapshot, read only 1`] = `
|
||||
Object {
|
||||
{
|
||||
"asFragment": [Function],
|
||||
"baseElement": <body>
|
||||
<div>
|
||||
|
||||
@@ -46,7 +46,7 @@ exports[`src/components/workspace return workspace and showcard 1`] = `
|
||||
/>
|
||||
<div>
|
||||
<span>
|
||||
Find Boards
|
||||
Find boards
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -73,6 +73,7 @@ exports[`src/components/workspace return workspace and showcard 1`] = `
|
||||
>
|
||||
<div
|
||||
data-rbd-draggable-context-id="1"
|
||||
data-rbd-draggable-id="categoryAttributeId1"
|
||||
>
|
||||
<div
|
||||
class="SidebarCategory"
|
||||
@@ -80,14 +81,16 @@ exports[`src/components/workspace return workspace and showcard 1`] = `
|
||||
<div
|
||||
class="categoryBoardsDroppableArea"
|
||||
data-rbd-droppable-context-id="1"
|
||||
data-rbd-droppable-id="categoryAttributeId1"
|
||||
>
|
||||
<div
|
||||
class="octo-sidebar-item category expanded active"
|
||||
class="octo-sidebar-item category expanded "
|
||||
>
|
||||
<div
|
||||
aria-describedby="rbd-hidden-text-1-hidden-text-5"
|
||||
class="octo-sidebar-title category-title"
|
||||
data-rbd-drag-handle-context-id="1"
|
||||
data-rbd-drag-handle-draggable-id="categoryAttributeId1"
|
||||
draggable="false"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@@ -463,7 +466,7 @@ exports[`src/components/workspace return workspace and showcard 1`] = `
|
||||
>
|
||||
<span
|
||||
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
|
||||
</span>
|
||||
@@ -875,7 +878,7 @@ exports[`src/components/workspace return workspace readonly and showcard 1`] = `
|
||||
>
|
||||
<span
|
||||
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
|
||||
</span>
|
||||
@@ -1097,7 +1100,7 @@ exports[`src/components/workspace should match snapshot 1`] = `
|
||||
/>
|
||||
<div>
|
||||
<span>
|
||||
Find Boards
|
||||
Find boards
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1124,6 +1127,7 @@ exports[`src/components/workspace should match snapshot 1`] = `
|
||||
>
|
||||
<div
|
||||
data-rbd-draggable-context-id="0"
|
||||
data-rbd-draggable-id="categoryAttributeId1"
|
||||
>
|
||||
<div
|
||||
class="SidebarCategory"
|
||||
@@ -1131,14 +1135,16 @@ exports[`src/components/workspace should match snapshot 1`] = `
|
||||
<div
|
||||
class="categoryBoardsDroppableArea"
|
||||
data-rbd-droppable-context-id="0"
|
||||
data-rbd-droppable-id="categoryAttributeId1"
|
||||
>
|
||||
<div
|
||||
class="octo-sidebar-item category expanded active"
|
||||
class="octo-sidebar-item category expanded "
|
||||
>
|
||||
<div
|
||||
aria-describedby="rbd-hidden-text-0-hidden-text-0"
|
||||
class="octo-sidebar-title category-title"
|
||||
data-rbd-drag-handle-context-id="0"
|
||||
data-rbd-drag-handle-draggable-id="categoryAttributeId1"
|
||||
draggable="false"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@@ -1514,7 +1520,7 @@ exports[`src/components/workspace should match snapshot 1`] = `
|
||||
>
|
||||
<span
|
||||
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
|
||||
</span>
|
||||
@@ -1926,7 +1932,7 @@ exports[`src/components/workspace should match snapshot with readonly 1`] = `
|
||||
>
|
||||
<span
|
||||
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
|
||||
</span>
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
import React, {ReactElement, ReactNode} from 'react'
|
||||
import {render, screen, waitFor} from '@testing-library/react'
|
||||
|
||||
import '@testing-library/jest-dom'
|
||||
|
||||
import {mocked} from 'jest-mock'
|
||||
|
||||
@@ -35,7 +34,7 @@ const wrap = (child: ReactNode): ReactElement => (
|
||||
)
|
||||
|
||||
jest.mock('src/mutator')
|
||||
const mockedMutator = mocked(mutator, true)
|
||||
const mockedMutator = mocked(mutator)
|
||||
|
||||
describe('components/addContentMenuItem', () => {
|
||||
beforeEach(() => {
|
||||
@@ -66,7 +65,7 @@ describe('components/addContentMenuItem', () => {
|
||||
)
|
||||
expect(container).toMatchSnapshot()
|
||||
const buttonElement = screen.getByRole('button', {name: 'text'})
|
||||
userEvent.click(buttonElement)
|
||||
await userEvent.click(buttonElement)
|
||||
await waitFor(() => expect(mockedMutator.insertBlock).toBeCalled())
|
||||
})
|
||||
|
||||
@@ -82,7 +81,7 @@ describe('components/addContentMenuItem', () => {
|
||||
)
|
||||
expect(container).toMatchSnapshot()
|
||||
const buttonElement = screen.getByRole('button', {name: 'checkbox'})
|
||||
userEvent.click(buttonElement)
|
||||
await userEvent.click(buttonElement)
|
||||
await waitFor(() => expect(mockedMutator.insertBlock).toBeCalled())
|
||||
})
|
||||
|
||||
@@ -98,11 +97,12 @@ describe('components/addContentMenuItem', () => {
|
||||
)
|
||||
expect(container).toMatchSnapshot()
|
||||
const buttonElement = screen.getByRole('button', {name: 'divider'})
|
||||
userEvent.click(buttonElement)
|
||||
await userEvent.click(buttonElement)
|
||||
await waitFor(() => expect(mockedMutator.insertBlock).toBeCalled())
|
||||
})
|
||||
|
||||
test('return an error and empty element from unknown type', () => {
|
||||
jest.spyOn(console, 'error').mockImplementation()
|
||||
const {container} = render(
|
||||
wrap(
|
||||
<AddContentMenuItem
|
||||
@@ -112,6 +112,8 @@ describe('components/addContentMenuItem', () => {
|
||||
/>,
|
||||
),
|
||||
)
|
||||
expect(console.error).toBeCalledWith(expect.stringContaining('addContentMenu, unknown content type: unknown'))
|
||||
expect(container).toMatchSnapshot()
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,16 +2,10 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react'
|
||||
import {
|
||||
fireEvent,
|
||||
render,
|
||||
screen,
|
||||
act
|
||||
} from '@testing-library/react'
|
||||
import {fireEvent, render, screen} from '@testing-library/react'
|
||||
|
||||
import userEvent from '@testing-library/user-event'
|
||||
|
||||
import '@testing-library/jest-dom'
|
||||
|
||||
import {mocked} from 'jest-mock'
|
||||
|
||||
@@ -27,7 +21,7 @@ const card = TestBlockFactory.createCard()
|
||||
const icon = '👍'
|
||||
|
||||
jest.mock('src/mutator')
|
||||
const mockedMutator = mocked(mutator, true)
|
||||
const mockedMutator = mocked(mutator)
|
||||
|
||||
describe('components/blockIconSelector', () => {
|
||||
beforeEach(() => {
|
||||
@@ -53,14 +47,14 @@ describe('components/blockIconSelector', () => {
|
||||
))
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
test('return menu on click', () => {
|
||||
test('return menu on click', async () => {
|
||||
const {container} = render(wrapIntl(
|
||||
<BlockIconSelector
|
||||
block={card}
|
||||
size='l'
|
||||
/>,
|
||||
))
|
||||
userEvent.click(screen.getByRole('button', {name: 'menuwrapper'}))
|
||||
await userEvent.click(screen.getByRole('button', {name: 'menuwrapper'}))
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
test('return no menu in readonly', () => {
|
||||
@@ -73,54 +67,50 @@ describe('components/blockIconSelector', () => {
|
||||
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(
|
||||
<BlockIconSelector
|
||||
block={card}
|
||||
size='l'
|
||||
/>,
|
||||
))
|
||||
userEvent.click(screen.getByRole('button', {name: 'menuwrapper'}))
|
||||
await userEvent.click(screen.getByRole('button', {name: 'menuwrapper'}))
|
||||
const buttonRandom = screen.queryByRole('button', {name: 'Random'})
|
||||
expect(buttonRandom).not.toBeNull()
|
||||
userEvent.click(buttonRandom!)
|
||||
await userEvent.click(buttonRandom!)
|
||||
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(
|
||||
<BlockIconSelector
|
||||
block={card}
|
||||
size='l'
|
||||
/>,
|
||||
))
|
||||
act(() => {
|
||||
userEvent.click(getByRole('button', {name: 'menuwrapper'}))
|
||||
})
|
||||
await userEvent.click(getByRole('button', {name: 'menuwrapper'}))
|
||||
const menuPicker = container.querySelector('div#pick')
|
||||
expect(menuPicker).not.toBeNull()
|
||||
|
||||
act(() => {
|
||||
fireEvent.mouseEnter(menuPicker!)
|
||||
})
|
||||
fireEvent.mouseEnter(menuPicker!)
|
||||
|
||||
const allButtonThumbUp = getAllByRole('button', {name: /thumbsup/i})
|
||||
userEvent.click(allButtonThumbUp[0])
|
||||
await userEvent.click(allButtonThumbUp[0])
|
||||
expect(mockedMutator.changeBlockIcon).toBeCalledTimes(1)
|
||||
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(
|
||||
<BlockIconSelector
|
||||
block={card}
|
||||
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'})
|
||||
expect(buttonRemove).not.toBeNull()
|
||||
userEvent.click(buttonRemove!)
|
||||
await userEvent.click(buttonRemove!)
|
||||
expect(mockedMutator.changeBlockIcon).toBeCalledTimes(1)
|
||||
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
|
||||
class="css-1f43avz-a11yText-A11yText"
|
||||
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
|
||||
aria-atomic="false"
|
||||
aria-live="polite"
|
||||
@@ -22,7 +33,7 @@ exports[`components/blocksEditor/blocksEditor should match snapshot on empty 1`]
|
||||
class="css-1f43avz-a11yText-A11yText"
|
||||
/>
|
||||
<div
|
||||
class=" css-4bb158-control"
|
||||
class=" css-1x912dq-control"
|
||||
>
|
||||
<div
|
||||
class=" css-30zlo3-ValueContainer"
|
||||
@@ -394,7 +405,18 @@ exports[`components/blocksEditor/blocksEditor should match snapshot with blocks
|
||||
<span
|
||||
class="css-1f43avz-a11yText-A11yText"
|
||||
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
|
||||
aria-atomic="false"
|
||||
aria-live="polite"
|
||||
@@ -402,7 +424,7 @@ exports[`components/blocksEditor/blocksEditor should match snapshot with blocks
|
||||
class="css-1f43avz-a11yText-A11yText"
|
||||
/>
|
||||
<div
|
||||
class=" css-4bb158-control"
|
||||
class=" css-1x912dq-control"
|
||||
>
|
||||
<div
|
||||
class=" css-30zlo3-ValueContainer"
|
||||
|
||||
@@ -76,7 +76,18 @@ exports[`components/blocksEditor/editor should match snapshot on empty 1`] = `
|
||||
<span
|
||||
class="css-1f43avz-a11yText-A11yText"
|
||||
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
|
||||
aria-atomic="false"
|
||||
aria-live="polite"
|
||||
@@ -84,7 +95,7 @@ exports[`components/blocksEditor/editor should match snapshot on empty 1`] = `
|
||||
class="css-1f43avz-a11yText-A11yText"
|
||||
/>
|
||||
<div
|
||||
class=" css-4bb158-control"
|
||||
class=" css-1x912dq-control"
|
||||
>
|
||||
<div
|
||||
class=" css-30zlo3-ValueContainer"
|
||||
|
||||
@@ -8,7 +8,18 @@ exports[`components/blocksEditor/rootInput should match Display snapshot 1`] = `
|
||||
<span
|
||||
class="css-1f43avz-a11yText-A11yText"
|
||||
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
|
||||
aria-atomic="false"
|
||||
aria-live="polite"
|
||||
@@ -16,7 +27,7 @@ exports[`components/blocksEditor/rootInput should match Display snapshot 1`] = `
|
||||
class="css-1f43avz-a11yText-A11yText"
|
||||
/>
|
||||
<div
|
||||
class=" css-4bb158-control"
|
||||
class=" css-1x912dq-control"
|
||||
>
|
||||
<div
|
||||
class=" css-30zlo3-ValueContainer"
|
||||
@@ -60,7 +71,18 @@ exports[`components/blocksEditor/rootInput should match Input snapshot 1`] = `
|
||||
<span
|
||||
class="css-1f43avz-a11yText-A11yText"
|
||||
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
|
||||
aria-atomic="false"
|
||||
aria-live="polite"
|
||||
@@ -68,7 +90,7 @@ exports[`components/blocksEditor/rootInput should match Input snapshot 1`] = `
|
||||
class="css-1f43avz-a11yText-A11yText"
|
||||
/>
|
||||
<div
|
||||
class=" css-4bb158-control"
|
||||
class=" css-1x912dq-control"
|
||||
>
|
||||
<div
|
||||
class=" css-30zlo3-ValueContainer"
|
||||
@@ -118,9 +140,18 @@ exports[`components/blocksEditor/rootInput should match Input snapshot with menu
|
||||
aria-live="polite"
|
||||
aria-relevant="additions text"
|
||||
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
|
||||
class=" css-4bb158-control"
|
||||
class=" css-1x912dq-control"
|
||||
>
|
||||
<div
|
||||
class=" css-30zlo3-ValueContainer"
|
||||
@@ -161,7 +192,7 @@ exports[`components/blocksEditor/rootInput should match Input snapshot with menu
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class=" css-1aj7brc"
|
||||
class=" css-u0i6pk-MenuPortal"
|
||||
>
|
||||
<div
|
||||
class=" css-1rsmi4x-menu"
|
||||
|
||||
@@ -10,7 +10,12 @@ import {
|
||||
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 BlockContent from './blockContent'
|
||||
@@ -141,26 +146,28 @@ describe('components/blocksEditor/blockContent', () => {
|
||||
|
||||
test('should call onSave on hit enter in the input', async () => {
|
||||
const onSave = jest.fn()
|
||||
await act(async () => {
|
||||
render(wrapDNDIntl(
|
||||
<ReduxProvider store={store}>
|
||||
<BlockContent
|
||||
boardId='fake-board-id'
|
||||
block={block}
|
||||
contentOrder={[block.id]}
|
||||
editing={block}
|
||||
setEditing={jest.fn()}
|
||||
setAfterBlock={jest.fn()}
|
||||
onSave={onSave}
|
||||
onMove={jest.fn()}
|
||||
/>
|
||||
</ReduxProvider>,
|
||||
))
|
||||
})
|
||||
|
||||
const {user} = setup(wrapDNDIntl(
|
||||
<ReduxProvider store={store}>
|
||||
<BlockContent
|
||||
boardId='fake-board-id'
|
||||
block={block}
|
||||
contentOrder={[block.id]}
|
||||
editing={block}
|
||||
setEditing={jest.fn()}
|
||||
setAfterBlock={jest.fn()}
|
||||
onSave={onSave}
|
||||
onMove={jest.fn()}
|
||||
/>
|
||||
</ReduxProvider>,
|
||||
))
|
||||
const input = screen.getByDisplayValue('Title')
|
||||
expect(onSave).not.toBeCalled()
|
||||
fireEvent.change(input, {target: {value: 'test'}})
|
||||
fireEvent.keyDown(input, {key: 'Enter'})
|
||||
await act(async () => {
|
||||
await user.clear(input)
|
||||
await user.type(input, 'test')
|
||||
await user.keyboard('{Enter}')
|
||||
})
|
||||
|
||||
expect(onSave).toBeCalledWith(expect.objectContaining({value: 'test'}))
|
||||
})
|
||||
|
||||
@@ -13,7 +13,7 @@ jest.mock('src/octoClient')
|
||||
|
||||
describe('components/blocksEditor/blocks/attachment', () => {
|
||||
test('should match Display snapshot', async () => {
|
||||
const mockedOcto = mocked(octoClient, true)
|
||||
const mockedOcto = mocked(octoClient)
|
||||
mockedOcto.getFileAsDataUrl.mockResolvedValue({url: 'test.jpg'})
|
||||
const Component = AttachmentBlock.Display
|
||||
const {container} = render(
|
||||
|
||||
@@ -13,7 +13,7 @@ jest.mock('src/octoClient')
|
||||
|
||||
describe('components/blocksEditor/blocks/image', () => {
|
||||
test('should match Display snapshot', async () => {
|
||||
const mockedOcto = mocked(octoClient, true)
|
||||
const mockedOcto = mocked(octoClient)
|
||||
mockedOcto.getFileAsDataUrl.mockResolvedValue({url: 'test.jpg'})
|
||||
const Component = ImageBlock.Display
|
||||
const {container} = render(
|
||||
|
||||
@@ -13,7 +13,7 @@ jest.mock('src/octoClient')
|
||||
|
||||
describe('components/blocksEditor/blocks/video', () => {
|
||||
test('should match Display snapshot', async () => {
|
||||
const mockedOcto = mocked(octoClient, true)
|
||||
const mockedOcto = mocked(octoClient)
|
||||
mockedOcto.getFileAsDataUrl.mockResolvedValue({url: 'test.jpg'})
|
||||
const Component = VideoBlock.Display
|
||||
const {container} = render(
|
||||
|
||||
@@ -10,9 +10,15 @@ import {
|
||||
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 {BlockData} from './blocks/types'
|
||||
import BlocksEditor from './blocksEditor'
|
||||
|
||||
@@ -95,28 +101,25 @@ describe('components/blocksEditor/blocksEditor', () => {
|
||||
|
||||
test('should call onBlockCreate after introduce text and hit enter', async () => {
|
||||
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 () => {
|
||||
render(wrapDNDIntl(
|
||||
<ReduxProvider store={store}>
|
||||
<BlocksEditor
|
||||
boardId='test-board'
|
||||
onBlockCreated={onBlockCreated}
|
||||
onBlockModified={jest.fn()}
|
||||
onBlockMoved={jest.fn()}
|
||||
blocks={[]}
|
||||
/>
|
||||
</ReduxProvider>,
|
||||
))
|
||||
await user.type(screen.getByRole('combobox'), '/title')
|
||||
await user.keyboard('{Enter}')
|
||||
await user.type(screen.getByRole('textbox'), 'test')
|
||||
await user.keyboard('{Enter}')
|
||||
})
|
||||
|
||||
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'}))
|
||||
})
|
||||
@@ -138,7 +141,7 @@ describe('components/blocksEditor/blocksEditor', () => {
|
||||
const input = screen.getByTestId('checkbox-check')
|
||||
expect(onBlockModified).not.toBeCalled()
|
||||
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 {Provider as ReduxProvider} from 'react-redux'
|
||||
import {
|
||||
render,
|
||||
screen,
|
||||
fireEvent,
|
||||
act
|
||||
} from '@testing-library/react'
|
||||
import {render, screen, 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 Editor from './editor'
|
||||
@@ -82,25 +82,20 @@ describe('components/blocksEditor/editor', () => {
|
||||
|
||||
test('should call onSave after introduce text and hit enter', async () => {
|
||||
const onSave = jest.fn()
|
||||
const {user} = setup(wrapDNDIntl(
|
||||
<ReduxProvider store={store}>
|
||||
<Editor
|
||||
boardId='fake-board-id'
|
||||
onSave={onSave}
|
||||
/>
|
||||
</ReduxProvider>,
|
||||
))
|
||||
await act(async () => {
|
||||
render(wrapDNDIntl(
|
||||
<ReduxProvider store={store}>
|
||||
<Editor
|
||||
boardId='fake-board-id'
|
||||
onSave={onSave}
|
||||
/>
|
||||
</ReduxProvider>,
|
||||
))
|
||||
await user.type(screen.getByRole('combobox'), '/title')
|
||||
await user.keyboard('{Enter}')
|
||||
await user.type(screen.getByRole('textbox'), 'test')
|
||||
await user.keyboard('{Enter}')
|
||||
})
|
||||
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'}))
|
||||
})
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import React, {useState} from 'react'
|
||||
import Select from 'react-select'
|
||||
import {CSSObject} from '@emotion/serialize'
|
||||
import Select, {StylesConfig} from 'react-select'
|
||||
|
||||
import {getSelectBaseStyle} from 'src/theme'
|
||||
|
||||
@@ -16,11 +15,11 @@ type Props = {
|
||||
value: string
|
||||
}
|
||||
|
||||
const baseStyles = getSelectBaseStyle()
|
||||
const baseStyles = getSelectBaseStyle<ContentType>()
|
||||
|
||||
const styles = {
|
||||
const styles: StylesConfig<ContentType> = {
|
||||
...baseStyles,
|
||||
control: (provided: CSSObject): CSSObject => ({
|
||||
control: (provided) => ({
|
||||
...provided,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
@@ -29,12 +28,12 @@ const styles = {
|
||||
color: 'rgb(var(--center-channel-color-rgb))',
|
||||
flexDirection: 'row',
|
||||
}),
|
||||
input: (provided: CSSObject): CSSObject => ({
|
||||
input: (provided) => ({
|
||||
...provided,
|
||||
background: 'rgb(var(--center-channel-bg-rgb))',
|
||||
color: 'rgb(var(--center-channel-color-rgb))',
|
||||
}),
|
||||
menu: (provided: CSSObject): CSSObject => ({
|
||||
menu: (provided) => ({
|
||||
...provided,
|
||||
minWidth: '100%',
|
||||
width: 'max-content',
|
||||
@@ -42,7 +41,7 @@ const styles = {
|
||||
left: '0',
|
||||
marginBottom: '0',
|
||||
}),
|
||||
menuPortal: (provided: CSSObject): CSSObject => ({
|
||||
menuPortal: (provided) => ({
|
||||
...provided,
|
||||
zIndex: 999,
|
||||
}),
|
||||
@@ -52,7 +51,7 @@ export default function RootInput(props: Props) {
|
||||
const [showMenu, setShowMenu] = useState(false)
|
||||
|
||||
return (
|
||||
<Select
|
||||
<Select<ContentType>
|
||||
styles={styles}
|
||||
components={{DropdownIndicator: () => null, IndicatorSeparator: () => null}}
|
||||
className='RootInput'
|
||||
@@ -62,8 +61,8 @@ export default function RootInput(props: Props) {
|
||||
menuPortalTarget={document.getElementById('focalboard-root-portal')}
|
||||
menuPosition={'fixed'}
|
||||
options={registry.list()}
|
||||
getOptionValue={(ct: ContentType) => ct.slashCommand}
|
||||
getOptionLabel={(ct: ContentType) => ct.slashCommand + ' Creates a new ' + ct.displayName + ' block.'}
|
||||
getOptionValue={(ct) => ct.slashCommand}
|
||||
getOptionLabel={(ct) => ct.slashCommand + ' Creates a new ' + ct.displayName + ' block.'}
|
||||
filterOption={(option: any, inputValue: string): boolean => {
|
||||
return inputValue.startsWith(option.value) || option.value.startsWith(inputValue)
|
||||
}}
|
||||
@@ -90,7 +89,7 @@ export default function RootInput(props: Props) {
|
||||
props.onChange('')
|
||||
}
|
||||
}}
|
||||
onFocus={(e: React.FocusEvent) => {
|
||||
onFocus={(e) => {
|
||||
const target = e.currentTarget
|
||||
target.scrollIntoView({block: 'center'})
|
||||
}}
|
||||
|
||||
@@ -21,7 +21,7 @@ import {wrapIntl} from 'src/testUtils'
|
||||
import BoardSelector from './boardSelector'
|
||||
|
||||
jest.mock('src/octoClient')
|
||||
const mockedOctoClient = mocked(octoClient, true)
|
||||
const mockedOctoClient = mocked(octoClient)
|
||||
|
||||
const wait = (ms: number) => {
|
||||
return new Promise<void>((resolve) => {
|
||||
|
||||
@@ -132,7 +132,7 @@ exports[`components/boardTemplateSelector/boardTemplateSelector a focalboard Plu
|
||||
class="CompassIcon icon-kanban"
|
||||
/>
|
||||
<span>
|
||||
Create empty board
|
||||
Create an empty board
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -284,7 +284,7 @@ exports[`components/boardTemplateSelector/boardTemplateSelector a focalboard Plu
|
||||
class="CompassIcon icon-kanban"
|
||||
/>
|
||||
<span>
|
||||
Create empty board
|
||||
Create an empty board
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -436,7 +436,7 @@ exports[`components/boardTemplateSelector/boardTemplateSelector a focalboard Plu
|
||||
class="CompassIcon icon-kanban"
|
||||
/>
|
||||
<span>
|
||||
Create empty board
|
||||
Create an empty board
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -195,7 +195,7 @@ exports[`components/boardTemplateSelector/boardTemplateSelectorPreview should ma
|
||||
>
|
||||
<span
|
||||
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
|
||||
</span>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import {
|
||||
act,
|
||||
render,
|
||||
screen,
|
||||
act,
|
||||
waitFor,
|
||||
within
|
||||
} from '@testing-library/react'
|
||||
@@ -50,11 +50,11 @@ jest.mock('src/mutator')
|
||||
jest.mock('src/utils')
|
||||
|
||||
jest.mock('src/telemetry/telemetryClient')
|
||||
const mockedTelemetry = mocked(TelemetryClient, true)
|
||||
const mockedTelemetry = mocked(TelemetryClient)
|
||||
|
||||
describe('components/boardTemplateSelector/boardTemplateSelector', () => {
|
||||
const mockedMutator = mocked(Mutator, true)
|
||||
const mockedOctoClient = mocked(client, true)
|
||||
const mockedMutator = mocked(Mutator)
|
||||
const mockedOctoClient = mocked(client)
|
||||
const team1: Team = {
|
||||
id: 'team-1',
|
||||
title: 'Team 1',
|
||||
@@ -194,7 +194,7 @@ describe('components/boardTemplateSelector/boardTemplateSelector', () => {
|
||||
), {wrapper: MemoryRouter})
|
||||
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 {container} = render(wrapDNDIntl(
|
||||
<ReduxProvider store={store}>
|
||||
@@ -204,10 +204,10 @@ describe('components/boardTemplateSelector/boardTemplateSelector', () => {
|
||||
), {wrapper: MemoryRouter})
|
||||
const divCloseButton = container.querySelector('div.toolbar .CloseIcon')
|
||||
expect(divCloseButton).not.toBeNull()
|
||||
userEvent.click(divCloseButton!)
|
||||
await userEvent.click(divCloseButton!)
|
||||
expect(onClose).toBeCalledTimes(1)
|
||||
})
|
||||
test('return BoardTemplateSelector and click new template', () => {
|
||||
test('return BoardTemplateSelector and click new template', async () => {
|
||||
render(wrapDNDIntl(
|
||||
<ReduxProvider store={store}>
|
||||
<BoardTemplateSelector onClose={jest.fn()}/>
|
||||
@@ -216,7 +216,7 @@ describe('components/boardTemplateSelector/boardTemplateSelector', () => {
|
||||
), {wrapper: MemoryRouter})
|
||||
const divNewTemplate = screen.getByText('Create new template').parentElement
|
||||
expect(divNewTemplate).not.toBeNull()
|
||||
userEvent.click(divNewTemplate!)
|
||||
await userEvent.click(divNewTemplate!)
|
||||
expect(mockedMutator.addEmptyBoardTemplate).toBeCalledTimes(1)
|
||||
})
|
||||
test('return BoardTemplateSelector and click empty board', async () => {
|
||||
@@ -230,9 +230,9 @@ describe('components/boardTemplateSelector/boardTemplateSelector', () => {
|
||||
,
|
||||
), {wrapper: MemoryRouter})
|
||||
|
||||
const divEmptyboard = screen.getByText('Create empty board').parentElement
|
||||
const divEmptyboard = screen.getByText('Create an empty board').parentElement
|
||||
expect(divEmptyboard).not.toBeNull()
|
||||
userEvent.click(divEmptyboard!)
|
||||
await userEvent.click(divEmptyboard!)
|
||||
expect(mockedMutator.addEmptyBoard).toBeCalledTimes(1)
|
||||
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)})
|
||||
const deleteIcon = screen.getByText(template1Title).parentElement?.querySelector('.DeleteIcon')
|
||||
expect(deleteIcon).not.toBeNull()
|
||||
act(() => {
|
||||
userEvent.click(deleteIcon!)
|
||||
})
|
||||
await act(() => userEvent.click(deleteIcon!))
|
||||
|
||||
const {getByText} = within(root)
|
||||
const deleteConfirm = getByText('Delete')
|
||||
expect(deleteConfirm).not.toBeNull()
|
||||
|
||||
await act(async () => {
|
||||
await userEvent.click(deleteConfirm!)
|
||||
})
|
||||
await act(() => userEvent.click(deleteConfirm!))
|
||||
|
||||
expect(mockedMutator.deleteBoard).toBeCalledTimes(1)
|
||||
})
|
||||
@@ -273,7 +269,7 @@ describe('components/boardTemplateSelector/boardTemplateSelector', () => {
|
||||
))
|
||||
const editIcon = screen.getByText(template1Title).parentElement?.querySelector('.EditIcon')
|
||||
expect(editIcon).not.toBeNull()
|
||||
userEvent.click(editIcon!)
|
||||
await userEvent.click(editIcon!)
|
||||
})
|
||||
test('return BoardTemplateSelector and click to add board from template', async () => {
|
||||
const newBoard = createBoard({id: 'new-board'} as Board)
|
||||
@@ -288,15 +284,12 @@ describe('components/boardTemplateSelector/boardTemplateSelector', () => {
|
||||
const divBoardToSelect = screen.getByText(template1Title).parentElement
|
||||
expect(divBoardToSelect).not.toBeNull()
|
||||
|
||||
act(() => {
|
||||
userEvent.click(divBoardToSelect!)
|
||||
})
|
||||
await userEvent.click(divBoardToSelect!)
|
||||
|
||||
const useTemplateButton = screen.getByText('Use this template').parentElement
|
||||
expect(useTemplateButton).not.toBeNull()
|
||||
act(() => {
|
||||
userEvent.click(useTemplateButton!)
|
||||
})
|
||||
|
||||
await userEvent.click(useTemplateButton!)
|
||||
|
||||
await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledTimes(1))
|
||||
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
|
||||
expect(divBoardToSelect).not.toBeNull()
|
||||
|
||||
act(() => {
|
||||
userEvent.click(divBoardToSelect!)
|
||||
})
|
||||
await userEvent.click(divBoardToSelect!)
|
||||
|
||||
|
||||
const useTemplateButton = screen.getByText('Use this template').parentElement
|
||||
expect(useTemplateButton).not.toBeNull()
|
||||
act(() => {
|
||||
userEvent.click(useTemplateButton!)
|
||||
})
|
||||
|
||||
await userEvent.click(useTemplateButton!)
|
||||
|
||||
await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledTimes(1))
|
||||
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
|
||||
expect(divBoardToSelect).not.toBeNull()
|
||||
|
||||
act(() => {
|
||||
userEvent.click(divBoardToSelect!)
|
||||
})
|
||||
|
||||
await userEvent.click(divBoardToSelect!)
|
||||
|
||||
const useTemplateButton = screen.getByText('Use this template').parentElement
|
||||
expect(useTemplateButton).not.toBeNull()
|
||||
act(() => {
|
||||
userEvent.click(useTemplateButton!)
|
||||
})
|
||||
|
||||
await userEvent.click(useTemplateButton!)
|
||||
|
||||
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(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
|
||||
expect(divBoardToSelect).not.toBeNull()
|
||||
|
||||
act(() => {
|
||||
userEvent.click(divBoardToSelect!)
|
||||
})
|
||||
await userEvent.click(divBoardToSelect!)
|
||||
|
||||
const useTemplateButton = screen.getByText('Use this template').parentElement
|
||||
expect(useTemplateButton).not.toBeNull()
|
||||
act(() => {
|
||||
userEvent.click(useTemplateButton!)
|
||||
})
|
||||
|
||||
|
||||
await userEvent.click(useTemplateButton!)
|
||||
|
||||
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(mockedTelemetry.trackEvent).toBeCalledWith('boards', 'createBoardViaTemplate', {boardTemplateId: 'template_id_2'}))
|
||||
|
||||
@@ -199,7 +199,7 @@ describe('components/boardTemplateSelector/boardTemplateSelectorItem', () => {
|
||||
</ReduxProvider>
|
||||
,
|
||||
))
|
||||
userEvent.click(container.querySelector('.BoardTemplateSelectorItem')!)
|
||||
await userEvent.click(container.querySelector('.BoardTemplateSelectorItem')!)
|
||||
expect(onSelect).toBeCalledTimes(1)
|
||||
expect(onSelect).toBeCalledWith(template)
|
||||
expect(onDelete).not.toBeCalled()
|
||||
@@ -222,7 +222,7 @@ describe('components/boardTemplateSelector/boardTemplateSelectorItem', () => {
|
||||
</ReduxProvider>
|
||||
,
|
||||
))
|
||||
userEvent.click(container.querySelector('.BoardTemplateSelectorItem .EditIcon')!)
|
||||
await userEvent.click(container.querySelector('.BoardTemplateSelectorItem .EditIcon')!)
|
||||
expect(onEdit).toBeCalledTimes(1)
|
||||
expect(onEdit).toBeCalledWith(template.id)
|
||||
expect(onSelect).not.toBeCalled()
|
||||
@@ -248,16 +248,12 @@ describe('components/boardTemplateSelector/boardTemplateSelectorItem', () => {
|
||||
</ReduxProvider>
|
||||
,
|
||||
), {container: document.body.appendChild(root)})
|
||||
act(() => {
|
||||
userEvent.click(root.querySelector('.BoardTemplateSelectorItem .DeleteIcon')!)
|
||||
})
|
||||
await act(() => userEvent.click(root.querySelector('.BoardTemplateSelectorItem .DeleteIcon')!))
|
||||
|
||||
expect(root).toMatchSnapshot()
|
||||
|
||||
const {getByText} = within(root)
|
||||
act(() => {
|
||||
userEvent.click(getByText('Delete')!)
|
||||
})
|
||||
await act(() => userEvent.click(getByText('Delete')!))
|
||||
|
||||
await waitFor(async () => expect(onDelete).toBeCalledTimes(1))
|
||||
await waitFor(async () => expect(onDelete).toBeCalledWith(template))
|
||||
|
||||
@@ -96,3 +96,9 @@ exports[`components/boardsUnfurl/BoardsUnfurl renders when limited 1`] = `
|
||||
</a>
|
||||
</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 {wrapIntl} from 'src/testUtils'
|
||||
|
||||
import {createBoardView} from 'src/blocks/boardView'
|
||||
|
||||
import BoardsUnfurl from './boardsUnfurl'
|
||||
|
||||
jest.mock('src/octoClient')
|
||||
jest.mock('src/utils')
|
||||
const mockedOctoClient = mocked(octoClient, true)
|
||||
const mockedUtils = mocked(Utils, true)
|
||||
const mockedOctoClient = mocked(octoClient)
|
||||
const mockedUtils = mocked(Utils)
|
||||
mockedUtils.createGuid = jest.requireActual('src/utils').Utils.createGuid
|
||||
mockedUtils.blockTypeToIDType = jest.requireActual('src/utils').Utils.blockTypeToIDType
|
||||
mockedUtils.displayDateTime = jest.requireActual('src/utils').Utils.displayDateTime
|
||||
@@ -114,5 +116,118 @@ describe('components/boardsUnfurl/BoardsUnfurl', () => {
|
||||
|
||||
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[]
|
||||
if (!firstCard || !fetchedBoard) {
|
||||
if (!firstCard || !fetchedBoard || firstCard.type !== 'card') {
|
||||
setLoading(false)
|
||||
return null
|
||||
}
|
||||
@@ -116,7 +116,7 @@ export const BoardsUnfurl = (props: Props): JSX.Element => {
|
||||
useWebsockets(currentTeamId, (wsClient: WSClient) => {
|
||||
const onChangeHandler = (_: WSClient, blocks: Block[]): void => {
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
@@ -143,7 +143,7 @@ describe('components/calculations/Calculation', () => {
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('should match snapshot - option change', () => {
|
||||
test('should match snapshot - option change', async () => {
|
||||
const onMenuOpen = jest.fn()
|
||||
const onMenuClose = jest.fn()
|
||||
const onChange = jest.fn()
|
||||
@@ -171,7 +171,7 @@ describe('components/calculations/Calculation', () => {
|
||||
)
|
||||
|
||||
const countMenuOption = container.querySelector('#react-select-2-option-1')
|
||||
userEvent.click(countMenuOption as Element)
|
||||
await userEvent.click(countMenuOption as Element)
|
||||
expect(container).toMatchSnapshot()
|
||||
expect(onMenuOpen).not.toBeCalled()
|
||||
expect(onMenuClose).toBeCalled()
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
// See LICENSE.txt for license information.
|
||||
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'
|
||||
|
||||
@@ -114,22 +113,22 @@ function generateTypesByOption(): Map<string, string[]> {
|
||||
return mapping
|
||||
}
|
||||
|
||||
const baseStyles = getSelectBaseStyle()
|
||||
const baseStyles = getSelectBaseStyle<Option>()
|
||||
|
||||
const styles = {
|
||||
const styles: StylesConfig<Option> = {
|
||||
...baseStyles,
|
||||
dropdownIndicator: (provided: CSSObject): CSSObject => ({
|
||||
...baseStyles.dropdownIndicator(provided),
|
||||
dropdownIndicator: (...props) => ({
|
||||
...baseStyles.dropdownIndicator?.(...props),
|
||||
pointerEvents: 'none',
|
||||
}),
|
||||
control: (): CSSObject => ({
|
||||
control: () => ({
|
||||
border: 0,
|
||||
width: '100%',
|
||||
margin: '0',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
}),
|
||||
menu: (provided: CSSObject): CSSObject => ({
|
||||
menu: (provided) => ({
|
||||
...provided,
|
||||
minWidth: '100%',
|
||||
width: 'max-content',
|
||||
@@ -137,15 +136,15 @@ const styles = {
|
||||
left: '0',
|
||||
marginBottom: '0',
|
||||
}),
|
||||
singleValue: (provided: CSSObject): CSSObject => ({
|
||||
...baseStyles.singleValue(provided),
|
||||
singleValue: (...props) => ({
|
||||
...baseStyles.singleValue?.(...props),
|
||||
opacity: '0.8',
|
||||
fontSize: '12px',
|
||||
right: '0',
|
||||
textTransform: 'uppercase',
|
||||
}),
|
||||
valueContainer: (provided: CSSObject): CSSObject => ({
|
||||
...baseStyles.valueContainer(provided),
|
||||
valueContainer: (...props) => ({
|
||||
...baseStyles.valueContainer?.(...props),
|
||||
display: 'none',
|
||||
pointerEvents: 'none',
|
||||
}),
|
||||
@@ -178,7 +177,7 @@ export const CalculationOptions = (props: BaseCalculationOptionProps): JSX.Eleme
|
||||
const intl = useIntl()
|
||||
|
||||
return (
|
||||
<Select
|
||||
<Select<Option>
|
||||
styles={styles}
|
||||
value={Options[props.value]}
|
||||
isMulti={false}
|
||||
|
||||
@@ -5,7 +5,6 @@ import {render} from '@testing-library/react'
|
||||
import {Provider as ReduxProvider} from 'react-redux'
|
||||
|
||||
import {TestBlockFactory} from 'src/test/testBlockFactory'
|
||||
import '@testing-library/jest-dom'
|
||||
import {wrapIntl, mockStateStore} from 'src/testUtils'
|
||||
import {IPropertyTemplate} from 'src/blocks/board'
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import '@testing-library/jest-dom'
|
||||
import {act, render} from '@testing-library/react'
|
||||
import React from 'react'
|
||||
import {Provider as ReduxProvider} from 'react-redux'
|
||||
|
||||
@@ -5,7 +5,6 @@ import React from 'react'
|
||||
import {Provider as ReduxProvider} from 'react-redux'
|
||||
|
||||
import {render, screen} from '@testing-library/react'
|
||||
import '@testing-library/jest-dom'
|
||||
|
||||
import {TestBlockFactory} from 'src/test/testBlockFactory'
|
||||
import {blocksById, mockStateStore, wrapDNDIntl} from 'src/testUtils'
|
||||
|
||||
@@ -171,7 +171,7 @@ exports[`components/cardDetail/CardDetail should render hidden view if limited 1
|
||||
<p
|
||||
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 />
|
||||
<a
|
||||
class="CardDetail__limited-link"
|
||||
@@ -480,7 +480,7 @@ exports[`components/cardDetail/CardDetail should show add properties tour tip 1`
|
||||
<div
|
||||
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
|
||||
class="tutorial-tour-tip__image"
|
||||
|
||||
@@ -437,7 +437,7 @@ exports[`components/cardDetail/cardDetailContentsMenu return cardDetailContentsM
|
||||
>
|
||||
<div
|
||||
aria-label="menuwrapper"
|
||||
class="MenuWrapper override menuOpened"
|
||||
class="MenuWrapper"
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
@@ -448,198 +448,6 @@ exports[`components/cardDetail/cardDetailContentsMenu return cardDetailContentsM
|
||||
Add content
|
||||
</span>
|
||||
</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>
|
||||
|
||||
@@ -10,7 +10,7 @@ exports[`components/cardDetail/CommentsList comments show up 1`] = `
|
||||
>
|
||||
<img
|
||||
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
|
||||
class="MarkdownEditor octo-editor newcomment "
|
||||
@@ -34,7 +34,7 @@ exports[`components/cardDetail/CommentsList comments show up 1`] = `
|
||||
>
|
||||
<img
|
||||
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
|
||||
class="comment-username"
|
||||
@@ -80,7 +80,7 @@ exports[`components/cardDetail/CommentsList comments show up 1`] = `
|
||||
>
|
||||
<img
|
||||
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
|
||||
class="comment-username"
|
||||
@@ -138,7 +138,7 @@ exports[`components/cardDetail/CommentsList comments show up in readonly mode 1`
|
||||
>
|
||||
<img
|
||||
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
|
||||
class="comment-username"
|
||||
@@ -170,7 +170,7 @@ exports[`components/cardDetail/CommentsList comments show up in readonly mode 1`
|
||||
>
|
||||
<img
|
||||
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
|
||||
class="comment-username"
|
||||
|
||||
@@ -26,7 +26,7 @@ import CardDetail from './cardDetail'
|
||||
global.fetch = FetchMock.fn
|
||||
jest.mock('src/octoClient')
|
||||
|
||||
const mockedOctoClient = mocked(octoClient, true)
|
||||
const mockedOctoClient = mocked(octoClient)
|
||||
|
||||
beforeEach(() => {
|
||||
FetchMock.fn.mockReset()
|
||||
@@ -299,7 +299,7 @@ describe('components/cardDetail/CardDetail', () => {
|
||||
expect(nextBtn).toBeDefined()
|
||||
expect(nextBtn).not.toBeNull()
|
||||
await act(async () => {
|
||||
userEvent.click(nextBtn!)
|
||||
await userEvent.click(nextBtn!)
|
||||
})
|
||||
expect(mockedOctoClient.patchUserConfig).toBeCalledWith(
|
||||
'user_id_1',
|
||||
@@ -408,7 +408,7 @@ describe('components/cardDetail/CardDetail', () => {
|
||||
expect(nextBtn).toBeDefined()
|
||||
expect(nextBtn).not.toBeNull()
|
||||
await act(async () => {
|
||||
userEvent.click(nextBtn!)
|
||||
await userEvent.click(nextBtn!)
|
||||
})
|
||||
expect(mockedOctoClient.patchUserConfig).toBeCalledWith(
|
||||
'user_id_1',
|
||||
@@ -521,7 +521,7 @@ describe('components/cardDetail/CardDetail', () => {
|
||||
expect(nextBtn).toBeDefined()
|
||||
expect(nextBtn).not.toBeNull()
|
||||
await act(async () => {
|
||||
userEvent.click(nextBtn!)
|
||||
await userEvent.click(nextBtn!)
|
||||
})
|
||||
expect(mockedOctoClient.patchUserConfig).toBeCalledWith(
|
||||
'user_id_1',
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import {act, render, screen} from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import {render, screen} from '@testing-library/react'
|
||||
import React, {ReactElement, ReactNode} from 'react'
|
||||
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'
|
||||
|
||||
@@ -36,22 +37,18 @@ describe('components/cardDetail/cardDetailContentsMenu', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
test('return cardDetailContentsMenu', () => {
|
||||
test('return cardDetailContentsMenu', async () => {
|
||||
const {container} = render(wrap(<CardDetailContentsMenu/>))
|
||||
const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})
|
||||
userEvent.click(buttonElement)
|
||||
await userEvent.click(buttonElement)
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('return cardDetailContentsMenu and add Text content', async () => {
|
||||
const {container} = render(wrap(<CardDetailContentsMenu/>))
|
||||
const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})
|
||||
userEvent.click(buttonElement)
|
||||
const {user, container} = setup(wrap(<CardDetailContentsMenu/>))
|
||||
await user.click(screen.getByRole('button', {name: 'menuwrapper'}))
|
||||
expect(container).toMatchSnapshot()
|
||||
await act(async () => {
|
||||
const buttonAddText = screen.getByRole('button', {name: 'text'})
|
||||
userEvent.click(buttonAddText)
|
||||
})
|
||||
await user.click(screen.getByRole('button', {name: 'text'}))
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
} from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import {mocked} from 'jest-mock'
|
||||
import '@testing-library/jest-dom'
|
||||
import {createIntl} from 'react-intl'
|
||||
|
||||
import configureStore from 'redux-mock-store'
|
||||
@@ -25,7 +24,7 @@ import {PropertyType} from 'src/properties/types'
|
||||
import CardDetailProperties from './cardDetailProperties'
|
||||
|
||||
jest.mock('src/mutator')
|
||||
const mockedMutator = mocked(mutator, true)
|
||||
const mockedMutator = mocked(mutator)
|
||||
|
||||
describe('components/cardDetail/CardDetailProperties', () => {
|
||||
const board = TestBlockFactory.createBoard()
|
||||
@@ -140,25 +139,25 @@ describe('components/cardDetail/CardDetailProperties', () => {
|
||||
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()
|
||||
|
||||
const menuElement = screen.getByRole('button', {name: 'Owner'})
|
||||
userEvent.click(menuElement)
|
||||
await act(() => userEvent.click(menuElement))
|
||||
|
||||
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('button', {name: /delete/i})).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show property types menu', () => {
|
||||
it('should show property types menu', async () => {
|
||||
const intl = createIntl({locale: 'en'})
|
||||
const {container} = renderComponent()
|
||||
|
||||
const menuElement = screen.getByRole('button', {name: /add a property/i})
|
||||
userEvent.click(menuElement)
|
||||
await act(() => userEvent.click(menuElement))
|
||||
expect(container).toMatchSnapshot()
|
||||
|
||||
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()
|
||||
|
||||
const menuElement = screen.getByRole('button', {name: 'Owner'})
|
||||
userEvent.click(menuElement)
|
||||
await act(() => userEvent.click(menuElement))
|
||||
|
||||
const typeProperty = screen.getByText(/Type: Select/i)
|
||||
expect(typeProperty).toBeInTheDocument()
|
||||
@@ -182,7 +181,7 @@ describe('components/cardDetail/CardDetailProperties', () => {
|
||||
fireEvent.mouseOver(typeProperty)
|
||||
|
||||
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('button', {name: /Change property/i})).toBeInTheDocument()
|
||||
@@ -192,7 +191,7 @@ describe('components/cardDetail/CardDetailProperties', () => {
|
||||
const result = renderComponent()
|
||||
|
||||
// rename to "Owner-Renamed"
|
||||
onPropertyRenameNoConfirmationDialog(result.container)
|
||||
await onPropertyRenameNoConfirmationDialog(result.container)
|
||||
const propertyTemplate = board.cardProperties[0]
|
||||
|
||||
// should be called once on confirming renaming the property
|
||||
@@ -204,12 +203,10 @@ describe('components/cardDetail/CardDetailProperties', () => {
|
||||
renderComponent()
|
||||
|
||||
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 () => {
|
||||
const numberType = screen.getByRole('button', {name: /number/i})
|
||||
userEvent.click(numberType)
|
||||
})
|
||||
await act( () => userEvent.click(numberType))
|
||||
|
||||
expect(mockedMutator.insertPropertyTemplate).toHaveBeenCalledTimes(1)
|
||||
|
||||
@@ -220,11 +217,11 @@ describe('components/cardDetail/CardDetailProperties', () => {
|
||||
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 container = result.container
|
||||
|
||||
openDeleteConfirmationDialog(container)
|
||||
await openDeleteConfirmationDialog(container)
|
||||
|
||||
const propertyTemplate = board.cardProperties[0]
|
||||
|
||||
@@ -232,48 +229,49 @@ describe('components/cardDetail/CardDetailProperties', () => {
|
||||
expect(confirmButton).toBeDefined()
|
||||
|
||||
//click delete button
|
||||
userEvent.click(confirmButton!)
|
||||
await act(() => userEvent.click(confirmButton!))
|
||||
|
||||
// should be called once on confirming delete
|
||||
expect(mockedMutator.deleteProperty).toBeCalledTimes(1)
|
||||
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 container = result.container
|
||||
|
||||
openDeleteConfirmationDialog(container)
|
||||
await openDeleteConfirmationDialog(container)
|
||||
|
||||
const cancelButton = result.getByTitle('Cancel')
|
||||
expect(cancelButton).toBeDefined()
|
||||
|
||||
userEvent.click(cancelButton!)
|
||||
await act(() => userEvent.click(cancelButton!))
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
function openDeleteConfirmationDialog(container: HTMLElement) {
|
||||
async function openDeleteConfirmationDialog(container: HTMLElement) {
|
||||
const propertyLabel = container.querySelector('.MenuWrapper')
|
||||
expect(propertyLabel).toBeDefined()
|
||||
userEvent.click(propertyLabel!)
|
||||
await act(() => userEvent.click(propertyLabel!))
|
||||
|
||||
const deleteOption = container.querySelector('.MenuOption.TextOption')
|
||||
expect(propertyLabel).toBeDefined()
|
||||
userEvent.click(deleteOption!)
|
||||
await act(() => userEvent.click(deleteOption!))
|
||||
|
||||
const confirmDialog = container.querySelector('.dialog.confirmation-dialog-box')
|
||||
expect(confirmDialog).toBeDefined()
|
||||
}
|
||||
|
||||
function onPropertyRenameNoConfirmationDialog(container: HTMLElement) {
|
||||
async function onPropertyRenameNoConfirmationDialog(container: HTMLElement) {
|
||||
const propertyLabel = container.querySelector('.MenuWrapper')
|
||||
expect(propertyLabel).toBeDefined()
|
||||
userEvent.click(propertyLabel!)
|
||||
await act(() => userEvent.click(propertyLabel!))
|
||||
|
||||
// write new name in the name text box
|
||||
const propertyNameInput = container.querySelector('.PropertyMenu.menu-textbox')
|
||||
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'
|
||||
|
||||
jest.mock('src/mutator')
|
||||
const mockedMutator = mocked(mutator, true)
|
||||
const mockedMutator = mocked(mutator)
|
||||
|
||||
const board = TestBlockFactory.createBoard()
|
||||
const card = TestBlockFactory.createCard(board)
|
||||
@@ -50,7 +50,7 @@ describe('components/cardDetail/comment', () => {
|
||||
}
|
||||
})
|
||||
|
||||
test('return comment', () => {
|
||||
test('return comment', async () => {
|
||||
const {container} = render(wrapIntl(
|
||||
<ReduxProvider store={store}>
|
||||
<Comment
|
||||
@@ -62,7 +62,7 @@ describe('components/cardDetail/comment', () => {
|
||||
</ReduxProvider>,
|
||||
))
|
||||
const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})
|
||||
userEvent.click(buttonElement)
|
||||
await userEvent.click(buttonElement)
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
@@ -80,7 +80,7 @@ describe('components/cardDetail/comment', () => {
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('return comment and delete comment', () => {
|
||||
test('return comment and delete comment', async () => {
|
||||
const {container} = render(wrapIntl(
|
||||
<ReduxProvider store={store}>
|
||||
<Comment
|
||||
@@ -92,15 +92,15 @@ describe('components/cardDetail/comment', () => {
|
||||
</ReduxProvider>,
|
||||
))
|
||||
const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})
|
||||
userEvent.click(buttonElement)
|
||||
await userEvent.click(buttonElement)
|
||||
expect(container).toMatchSnapshot()
|
||||
const buttonDelete = screen.getByRole('button', {name: 'Delete'})
|
||||
userEvent.click(buttonDelete)
|
||||
await userEvent.click(buttonDelete)
|
||||
expect(mockedMutator.deleteBlock).toBeCalledTimes(1)
|
||||
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 {container} = render(wrapIntl(
|
||||
<ReduxProvider store={localStore}>
|
||||
@@ -113,11 +113,11 @@ describe('components/cardDetail/comment', () => {
|
||||
</ReduxProvider>,
|
||||
))
|
||||
const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})
|
||||
userEvent.click(buttonElement)
|
||||
await userEvent.click(buttonElement)
|
||||
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 {container} = render(wrapIntl(
|
||||
<ReduxProvider store={localStore}>
|
||||
@@ -132,7 +132,7 @@ describe('components/cardDetail/comment', () => {
|
||||
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 {container} = render(wrapIntl(
|
||||
<ReduxProvider store={localStore}>
|
||||
@@ -145,10 +145,10 @@ describe('components/cardDetail/comment', () => {
|
||||
</ReduxProvider>,
|
||||
))
|
||||
const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})
|
||||
userEvent.click(buttonElement)
|
||||
await userEvent.click(buttonElement)
|
||||
expect(container).toMatchSnapshot()
|
||||
const buttonDelete = screen.getByRole('button', {name: 'Delete'})
|
||||
userEvent.click(buttonDelete)
|
||||
await userEvent.click(buttonDelete)
|
||||
expect(mockedMutator.deleteBlock).toBeCalledTimes(1)
|
||||
expect(mockedMutator.deleteBlock).toBeCalledWith(comment)
|
||||
})
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import '@testing-library/jest-dom'
|
||||
import {act, render, screen} from '@testing-library/react'
|
||||
import {render, screen} from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
|
||||
import React from 'react'
|
||||
@@ -23,9 +22,9 @@ jest.mock('src/octoClient')
|
||||
jest.mock('src/utils')
|
||||
jest.mock('draft-js/lib/generateRandomKey', () => () => '123')
|
||||
|
||||
const mockedUtils = mocked(Utils, true)
|
||||
const mockedMutator = mocked(mutator, true)
|
||||
const mockedOctoClient = mocked(octoClient, true)
|
||||
const mockedUtils = mocked(Utils)
|
||||
const mockedMutator = mocked(mutator)
|
||||
const mockedOctoClient = mocked(octoClient)
|
||||
mockedUtils.createGuid.mockReturnValue('test-id')
|
||||
|
||||
beforeAll(() => {
|
||||
@@ -94,135 +93,116 @@ describe('components/cardDialog', () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
test('should match snapshot', async () => {
|
||||
let container
|
||||
await act(async () => {
|
||||
const result = render(wrapDNDIntl(
|
||||
<ReduxProvider store={store}>
|
||||
<CardDialog
|
||||
board={board}
|
||||
activeView={boardView}
|
||||
views={[boardView]}
|
||||
cards={[card]}
|
||||
cardId={card.id}
|
||||
onClose={jest.fn()}
|
||||
showCard={jest.fn()}
|
||||
readonly={false}
|
||||
/>
|
||||
</ReduxProvider>,
|
||||
))
|
||||
container = result.container
|
||||
})
|
||||
|
||||
const {container} = render(wrapDNDIntl(
|
||||
<ReduxProvider store={store}>
|
||||
<CardDialog
|
||||
board={board}
|
||||
activeView={boardView}
|
||||
views={[boardView]}
|
||||
cards={[card]}
|
||||
cardId={card.id}
|
||||
onClose={jest.fn()}
|
||||
showCard={jest.fn()}
|
||||
readonly={false}
|
||||
/>
|
||||
</ReduxProvider>,
|
||||
))
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
test('should match snapshot without permissions', async () => {
|
||||
let container
|
||||
const localStore = mockStateStore([], {...state, teams: {current: undefined}})
|
||||
await act(async () => {
|
||||
const result = render(wrapDNDIntl(
|
||||
<ReduxProvider store={localStore}>
|
||||
<CardDialog
|
||||
board={board}
|
||||
activeView={boardView}
|
||||
views={[boardView]}
|
||||
cards={[card]}
|
||||
cardId={card.id}
|
||||
onClose={jest.fn()}
|
||||
showCard={jest.fn()}
|
||||
readonly={false}
|
||||
/>
|
||||
</ReduxProvider>,
|
||||
))
|
||||
container = result.container
|
||||
})
|
||||
const {container} = render(wrapDNDIntl(
|
||||
<ReduxProvider store={localStore}>
|
||||
<CardDialog
|
||||
board={board}
|
||||
activeView={boardView}
|
||||
views={[boardView]}
|
||||
cards={[card]}
|
||||
cardId={card.id}
|
||||
onClose={jest.fn()}
|
||||
showCard={jest.fn()}
|
||||
readonly={false}
|
||||
/>
|
||||
</ReduxProvider>,
|
||||
))
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
test('return a cardDialog readonly', async () => {
|
||||
let container
|
||||
await act(async () => {
|
||||
const result = render(wrapDNDIntl(
|
||||
<ReduxProvider store={store}>
|
||||
<CardDialog
|
||||
board={board}
|
||||
activeView={boardView}
|
||||
views={[boardView]}
|
||||
cards={[card]}
|
||||
cardId={card.id}
|
||||
onClose={jest.fn()}
|
||||
showCard={jest.fn()}
|
||||
readonly={true}
|
||||
/>
|
||||
</ReduxProvider>,
|
||||
))
|
||||
container = result.container
|
||||
})
|
||||
const {container} = render(wrapDNDIntl(
|
||||
<ReduxProvider store={store}>
|
||||
<CardDialog
|
||||
board={board}
|
||||
activeView={boardView}
|
||||
views={[boardView]}
|
||||
cards={[card]}
|
||||
cardId={card.id}
|
||||
onClose={jest.fn()}
|
||||
showCard={jest.fn()}
|
||||
readonly={true}
|
||||
/>
|
||||
</ReduxProvider>,
|
||||
))
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
test('return cardDialog and do a close action', async () => {
|
||||
const closeFn = jest.fn()
|
||||
await act(async () => {
|
||||
render(wrapDNDIntl(
|
||||
<ReduxProvider store={store}>
|
||||
<CardDialog
|
||||
board={board}
|
||||
activeView={boardView}
|
||||
views={[boardView]}
|
||||
cards={[card]}
|
||||
cardId={card.id}
|
||||
onClose={closeFn}
|
||||
showCard={jest.fn()}
|
||||
readonly={false}
|
||||
/>
|
||||
</ReduxProvider>,
|
||||
))
|
||||
})
|
||||
render(wrapDNDIntl(
|
||||
<ReduxProvider store={store}>
|
||||
<CardDialog
|
||||
board={board}
|
||||
activeView={boardView}
|
||||
views={[boardView]}
|
||||
cards={[card]}
|
||||
cardId={card.id}
|
||||
onClose={closeFn}
|
||||
showCard={jest.fn()}
|
||||
readonly={false}
|
||||
/>
|
||||
</ReduxProvider>,
|
||||
))
|
||||
const buttonElement = screen.getByRole('button', {name: 'Close dialog'})
|
||||
userEvent.click(buttonElement)
|
||||
await userEvent.click(buttonElement)
|
||||
expect(closeFn).toBeCalledTimes(1)
|
||||
})
|
||||
test('return cardDialog menu content', async () => {
|
||||
let container
|
||||
await act(async () => {
|
||||
const result = render(wrapDNDIntl(
|
||||
<ReduxProvider store={store}>
|
||||
<CardDialog
|
||||
board={board}
|
||||
activeView={boardView}
|
||||
views={[boardView]}
|
||||
cards={[card]}
|
||||
cardId={card.id}
|
||||
onClose={jest.fn()}
|
||||
showCard={jest.fn()}
|
||||
readonly={false}
|
||||
/>
|
||||
</ReduxProvider>,
|
||||
))
|
||||
container = result.container
|
||||
})
|
||||
const {container} = render(wrapDNDIntl(
|
||||
<ReduxProvider store={store}>
|
||||
<CardDialog
|
||||
board={board}
|
||||
activeView={boardView}
|
||||
views={[boardView]}
|
||||
cards={[card]}
|
||||
cardId={card.id}
|
||||
onClose={jest.fn()}
|
||||
showCard={jest.fn()}
|
||||
readonly={false}
|
||||
/>
|
||||
</ReduxProvider>,
|
||||
))
|
||||
const buttonMenu = screen.getAllByRole('button', {name: 'menuwrapper'})[0]
|
||||
userEvent.click(buttonMenu)
|
||||
await userEvent.click(buttonMenu)
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
test('return cardDialog menu content and verify delete action', async () => {
|
||||
await act(async () => {
|
||||
render(wrapDNDIntl(
|
||||
<ReduxProvider store={store}>
|
||||
<CardDialog
|
||||
board={board}
|
||||
activeView={boardView}
|
||||
views={[boardView]}
|
||||
cards={[card]}
|
||||
cardId={card.id}
|
||||
onClose={jest.fn()}
|
||||
showCard={jest.fn()}
|
||||
readonly={false}
|
||||
/>
|
||||
</ReduxProvider>,
|
||||
))
|
||||
})
|
||||
render(wrapDNDIntl(
|
||||
<ReduxProvider store={store}>
|
||||
<CardDialog
|
||||
board={board}
|
||||
activeView={boardView}
|
||||
views={[boardView]}
|
||||
cards={[card]}
|
||||
cardId={card.id}
|
||||
onClose={jest.fn()}
|
||||
showCard={jest.fn()}
|
||||
readonly={false}
|
||||
/>
|
||||
</ReduxProvider>,
|
||||
))
|
||||
const buttonMenu = screen.getAllByRole('button', {name: 'menuwrapper'})[0]
|
||||
userEvent.click(buttonMenu)
|
||||
await userEvent.click(buttonMenu)
|
||||
const buttonDelete = screen.getByRole('button', {name: 'Delete'})
|
||||
userEvent.click(buttonDelete)
|
||||
await userEvent.click(buttonDelete)
|
||||
|
||||
const confirmDialog = screen.getByTitle('Confirmation Dialog Box')
|
||||
expect(confirmDialog).toBeDefined()
|
||||
@@ -231,36 +211,32 @@ describe('components/cardDialog', () => {
|
||||
expect(confirmButton).toBeDefined()
|
||||
|
||||
//click delete button
|
||||
userEvent.click(confirmButton!)
|
||||
await userEvent.click(confirmButton!)
|
||||
|
||||
// should be called once on confirming delete
|
||||
expect(mockedMutator.deleteBlock).toBeCalledTimes(1)
|
||||
})
|
||||
|
||||
test('return cardDialog menu content and cancel delete confirmation do nothing', async () => {
|
||||
let container
|
||||
await act(async () => {
|
||||
const result = render(wrapDNDIntl(
|
||||
<ReduxProvider store={store}>
|
||||
<CardDialog
|
||||
board={board}
|
||||
activeView={boardView}
|
||||
views={[boardView]}
|
||||
cards={[card]}
|
||||
cardId={card.id}
|
||||
onClose={jest.fn()}
|
||||
showCard={jest.fn()}
|
||||
readonly={false}
|
||||
/>
|
||||
</ReduxProvider>,
|
||||
))
|
||||
container = result.container
|
||||
})
|
||||
const {container} = render(wrapDNDIntl(
|
||||
<ReduxProvider store={store}>
|
||||
<CardDialog
|
||||
board={board}
|
||||
activeView={boardView}
|
||||
views={[boardView]}
|
||||
cards={[card]}
|
||||
cardId={card.id}
|
||||
onClose={jest.fn()}
|
||||
showCard={jest.fn()}
|
||||
readonly={false}
|
||||
/>
|
||||
</ReduxProvider>,
|
||||
))
|
||||
|
||||
const buttonMenu = screen.getAllByRole('button', {name: 'menuwrapper'})[0]
|
||||
userEvent.click(buttonMenu)
|
||||
await userEvent.click(buttonMenu)
|
||||
const buttonDelete = screen.getByRole('button', {name: 'Delete'})
|
||||
userEvent.click(buttonDelete)
|
||||
await userEvent.click(buttonDelete)
|
||||
|
||||
const confirmDialog = screen.getByTitle('Confirmation Dialog Box')
|
||||
expect(confirmDialog).toBeDefined()
|
||||
@@ -269,57 +245,53 @@ describe('components/cardDialog', () => {
|
||||
expect(cancelButton).toBeDefined()
|
||||
|
||||
//click delete button
|
||||
userEvent.click(cancelButton!)
|
||||
await userEvent.click(cancelButton!)
|
||||
|
||||
// should do nothing on cancel delete dialog
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('return cardDialog menu content and do a New template from card', async () => {
|
||||
await act(async () => {
|
||||
render(wrapDNDIntl(
|
||||
<ReduxProvider store={store}>
|
||||
<CardDialog
|
||||
board={board}
|
||||
activeView={boardView}
|
||||
views={[boardView]}
|
||||
cards={[card]}
|
||||
cardId={card.id}
|
||||
onClose={jest.fn()}
|
||||
showCard={jest.fn()}
|
||||
readonly={false}
|
||||
/>
|
||||
</ReduxProvider>,
|
||||
))
|
||||
})
|
||||
render(wrapDNDIntl(
|
||||
<ReduxProvider store={store}>
|
||||
<CardDialog
|
||||
board={board}
|
||||
activeView={boardView}
|
||||
views={[boardView]}
|
||||
cards={[card]}
|
||||
cardId={card.id}
|
||||
onClose={jest.fn()}
|
||||
showCard={jest.fn()}
|
||||
readonly={false}
|
||||
/>
|
||||
</ReduxProvider>,
|
||||
))
|
||||
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'})
|
||||
userEvent.click(buttonTemplate)
|
||||
await userEvent.click(buttonTemplate)
|
||||
expect(mockedMutator.duplicateCard).toBeCalledTimes(1)
|
||||
})
|
||||
|
||||
test('return cardDialog menu content and do a copy Link', async () => {
|
||||
await act(async () => {
|
||||
render(wrapDNDIntl(
|
||||
<ReduxProvider store={store}>
|
||||
<CardDialog
|
||||
board={board}
|
||||
activeView={boardView}
|
||||
views={[boardView]}
|
||||
cards={[card]}
|
||||
cardId={card.id}
|
||||
onClose={jest.fn()}
|
||||
showCard={jest.fn()}
|
||||
readonly={false}
|
||||
/>
|
||||
</ReduxProvider>,
|
||||
))
|
||||
})
|
||||
render(wrapDNDIntl(
|
||||
<ReduxProvider store={store}>
|
||||
<CardDialog
|
||||
board={board}
|
||||
activeView={boardView}
|
||||
views={[boardView]}
|
||||
cards={[card]}
|
||||
cardId={card.id}
|
||||
onClose={jest.fn()}
|
||||
showCard={jest.fn()}
|
||||
readonly={false}
|
||||
/>
|
||||
</ReduxProvider>,
|
||||
))
|
||||
const buttonMenu = screen.getAllByRole('button', {name: 'menuwrapper'})[0]
|
||||
userEvent.click(buttonMenu)
|
||||
await userEvent.click(buttonMenu)
|
||||
const buttonCopy = screen.getByRole('button', {name: 'Copy link'})
|
||||
userEvent.click(buttonCopy)
|
||||
await userEvent.click(buttonCopy)
|
||||
expect(mockedUtils.copyTextToClipboard).toBeCalledTimes(1)
|
||||
})
|
||||
|
||||
@@ -338,24 +310,21 @@ describe('components/cardDialog', () => {
|
||||
|
||||
const newStore = mockStateStore([], newState)
|
||||
|
||||
let container
|
||||
await act(async () => {
|
||||
const result = render(wrapDNDIntl(
|
||||
<ReduxProvider store={newStore}>
|
||||
<CardDialog
|
||||
board={board}
|
||||
activeView={boardView}
|
||||
views={[boardView]}
|
||||
cards={[card]}
|
||||
cardId={card.id}
|
||||
onClose={jest.fn()}
|
||||
showCard={jest.fn()}
|
||||
readonly={false}
|
||||
/>
|
||||
</ReduxProvider>,
|
||||
))
|
||||
container = result.container
|
||||
})
|
||||
const {container} = render(wrapDNDIntl(
|
||||
<ReduxProvider store={newStore}>
|
||||
<CardDialog
|
||||
board={board}
|
||||
activeView={boardView}
|
||||
views={[boardView]}
|
||||
cards={[card]}
|
||||
cardId={card.id}
|
||||
onClose={jest.fn()}
|
||||
showCard={jest.fn()}
|
||||
readonly={false}
|
||||
/>
|
||||
</ReduxProvider>,
|
||||
))
|
||||
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
@@ -373,24 +342,21 @@ describe('components/cardDialog', () => {
|
||||
|
||||
const newStore = mockStateStore([], newState)
|
||||
|
||||
let container
|
||||
await act(async () => {
|
||||
const result = render(wrapDNDIntl(
|
||||
<ReduxProvider store={newStore}>
|
||||
<CardDialog
|
||||
board={board}
|
||||
activeView={boardView}
|
||||
views={[boardView]}
|
||||
cards={[limitedCard]}
|
||||
cardId={limitedCard.id}
|
||||
onClose={jest.fn()}
|
||||
showCard={jest.fn()}
|
||||
readonly={false}
|
||||
/>
|
||||
</ReduxProvider>,
|
||||
))
|
||||
container = result.container
|
||||
})
|
||||
const {container} = render(wrapDNDIntl(
|
||||
<ReduxProvider store={newStore}>
|
||||
<CardDialog
|
||||
board={board}
|
||||
activeView={boardView}
|
||||
views={[boardView]}
|
||||
cards={[limitedCard]}
|
||||
cardId={limitedCard.id}
|
||||
onClose={jest.fn()}
|
||||
showCard={jest.fn()}
|
||||
readonly={false}
|
||||
/>
|
||||
</ReduxProvider>,
|
||||
))
|
||||
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import {
|
||||
act,
|
||||
fireEvent,
|
||||
render,
|
||||
screen,
|
||||
within,
|
||||
act
|
||||
within
|
||||
} from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import React from 'react'
|
||||
@@ -38,9 +38,9 @@ jest.mock('src/octoClient')
|
||||
jest.mock('src/mutator')
|
||||
jest.mock('src/telemetry/telemetryClient')
|
||||
jest.mock('draft-js/lib/generateRandomKey', () => () => '123')
|
||||
const mockedUtils = mocked(Utils, true)
|
||||
const mockedMutator = mocked(Mutator, true)
|
||||
const mockedOctoClient = mocked(octoClient, true)
|
||||
const mockedUtils = mocked(Utils)
|
||||
const mockedMutator = mocked(Mutator)
|
||||
const mockedOctoClient = mocked(octoClient)
|
||||
mockedUtils.createGuid.mockReturnValue('test-id')
|
||||
mockedUtils.generateClassName = jest.requireActual('src/utils').Utils.generateClassName
|
||||
describe('components/centerPanel', () => {
|
||||
@@ -234,7 +234,7 @@ describe('components/centerPanel', () => {
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
describe('return centerPanel and', () => {
|
||||
test('select one card and click background', () => {
|
||||
test('select one card and click background', async () => {
|
||||
activeView.fields.viewType = 'table'
|
||||
const {container} = render(wrapDNDIntl(
|
||||
<ReduxProvider store={store}>
|
||||
@@ -255,13 +255,13 @@ describe('components/centerPanel', () => {
|
||||
//select card
|
||||
const cardElement = screen.getByRole('textbox', {name: 'card1'})
|
||||
expect(cardElement).not.toBeNull()
|
||||
userEvent.click(cardElement, {shiftKey: true})
|
||||
fireEvent.click(cardElement, {shiftKey: true})
|
||||
expect(container).toMatchSnapshot()
|
||||
|
||||
//background
|
||||
const boardElement = container.querySelector('.BoardComponent')
|
||||
expect(boardElement).not.toBeNull()
|
||||
userEvent.click(boardElement!)
|
||||
fireEvent.click(boardElement!)
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
@@ -288,7 +288,7 @@ describe('components/centerPanel', () => {
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('press touch esc for one card selected', () => {
|
||||
test('press touch esc for one card selected', async () => {
|
||||
activeView.fields.viewType = 'table'
|
||||
const {container, baseElement} = render(wrapDNDIntl(
|
||||
<ReduxProvider store={store}>
|
||||
@@ -306,11 +306,9 @@ describe('components/centerPanel', () => {
|
||||
</ReduxProvider>,
|
||||
))
|
||||
|
||||
act(() => {
|
||||
const cardElement = screen.getByRole('textbox', {name: 'card1'})
|
||||
expect(cardElement.parentNode).not.toBeNull()
|
||||
userEvent.click(cardElement as HTMLElement, {shiftKey: true})
|
||||
})
|
||||
const cardElement = screen.getByRole('textbox', {name: 'card1'})
|
||||
expect(cardElement.parentNode).not.toBeNull()
|
||||
fireEvent.click(cardElement, {shiftKey: true})
|
||||
expect(container).toMatchSnapshot()
|
||||
|
||||
//escape
|
||||
@@ -335,27 +333,23 @@ describe('components/centerPanel', () => {
|
||||
</ReduxProvider>,
|
||||
))
|
||||
|
||||
act(() => {
|
||||
//select card1
|
||||
const card1Element = screen.getByRole('textbox', {name: 'card1'})
|
||||
expect(card1Element).not.toBeNull()
|
||||
userEvent.click(card1Element, {shiftKey: true})
|
||||
})
|
||||
//select card1
|
||||
const card1Element = screen.getByRole('textbox', {name: 'card1'})
|
||||
expect(card1Element).not.toBeNull()
|
||||
fireEvent.click(card1Element, {shiftKey: true})
|
||||
expect(container).toMatchSnapshot()
|
||||
|
||||
act(() => {
|
||||
//select card2
|
||||
const card2Element = screen.getByRole('textbox', {name: 'card2'})
|
||||
expect(card2Element).not.toBeNull()
|
||||
userEvent.click(card2Element, {shiftKey: true, ctrlKey: true})
|
||||
})
|
||||
//select card2
|
||||
const card2Element = screen.getByRole('textbox', {name: 'card2'})
|
||||
expect(card2Element).not.toBeNull()
|
||||
fireEvent.click(card2Element, {shiftKey: true, ctrlKey: true})
|
||||
expect(container).toMatchSnapshot()
|
||||
|
||||
//escape
|
||||
fireEvent.keyDown(baseElement, {keyCode: 27})
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
test('press touch del for one card selected', () => {
|
||||
test('press touch del for one card selected', async () => {
|
||||
activeView.fields.viewType = 'table'
|
||||
const {container, baseElement} = render(wrapDNDIntl(
|
||||
<ReduxProvider store={store}>
|
||||
@@ -372,19 +366,18 @@ describe('components/centerPanel', () => {
|
||||
/>
|
||||
</ReduxProvider>,
|
||||
))
|
||||
act(() => {
|
||||
const cardElement = screen.getByRole('textbox', {name: 'card1'})
|
||||
expect(cardElement).not.toBeNull()
|
||||
userEvent.click(cardElement, {shiftKey: true})
|
||||
})
|
||||
const cardElement = screen.getByRole('textbox', {name: 'card1'})
|
||||
expect(cardElement).not.toBeNull()
|
||||
fireEvent.click(cardElement, {shiftKey: true})
|
||||
expect(container).toMatchSnapshot()
|
||||
|
||||
//delete
|
||||
fireEvent.keyDown(baseElement, {keyCode: 8})
|
||||
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'
|
||||
|
||||
const {container, baseElement} = render(wrapDNDIntl(
|
||||
<ReduxProvider store={store}>
|
||||
<CenterPanel
|
||||
@@ -400,18 +393,16 @@ describe('components/centerPanel', () => {
|
||||
/>
|
||||
</ReduxProvider>,
|
||||
))
|
||||
act(() => {
|
||||
const cardElement = screen.getByRole('textbox', {name: 'card1'})
|
||||
expect(cardElement).not.toBeNull()
|
||||
userEvent.click(cardElement, {shiftKey: true})
|
||||
})
|
||||
const cardElement = screen.getByRole('textbox', {name: 'card1'})
|
||||
expect(cardElement).not.toBeNull()
|
||||
fireEvent.click(cardElement, {shiftKey: true})
|
||||
expect(container).toMatchSnapshot()
|
||||
|
||||
//ctrl+d
|
||||
fireEvent.keyDown(baseElement, {ctrlKey: true, keyCode: 68})
|
||||
expect(mockedMutator.performAsUndoGroup).toBeCalledTimes(1)
|
||||
})
|
||||
test('click on card to show card', () => {
|
||||
test('click on card to show card', async () => {
|
||||
activeView.fields.viewType = 'board'
|
||||
const mockedShowCard = jest.fn()
|
||||
const {container} = render(wrapDNDIntl(
|
||||
@@ -433,11 +424,11 @@ describe('components/centerPanel', () => {
|
||||
const kanbanCardElements = container.querySelectorAll('.KanbanCard')
|
||||
expect(kanbanCardElements).not.toBeNull()
|
||||
const kanbanCardElement = kanbanCardElements[0]
|
||||
userEvent.click(kanbanCardElement)
|
||||
await userEvent.click(kanbanCardElement)
|
||||
expect(container).toMatchSnapshot()
|
||||
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'
|
||||
const {container} = render(wrapDNDIntl(
|
||||
<ReduxProvider store={store}>
|
||||
@@ -456,10 +447,10 @@ describe('components/centerPanel', () => {
|
||||
))
|
||||
const buttonWithMenuElement = container.querySelector('.ButtonWithMenu')
|
||||
expect(buttonWithMenuElement).not.toBeNull()
|
||||
userEvent.click(buttonWithMenuElement!)
|
||||
await act(() => userEvent.click(buttonWithMenuElement!))
|
||||
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'
|
||||
const {container} = render(wrapDNDIntl(
|
||||
<ReduxProvider store={store}>
|
||||
@@ -478,13 +469,13 @@ describe('components/centerPanel', () => {
|
||||
))
|
||||
const elementMenuWrapper = container.querySelector('.ButtonWithMenu > div.MenuWrapper')
|
||||
expect(elementMenuWrapper).not.toBeNull()
|
||||
userEvent.click(elementMenuWrapper!)
|
||||
await act(() => userEvent.click(elementMenuWrapper!))
|
||||
const buttonNewTemplate = within(elementMenuWrapper!.parentElement!).getByRole('button', {name: 'New template'})
|
||||
userEvent.click(buttonNewTemplate)
|
||||
await act(() => userEvent.click(buttonNewTemplate))
|
||||
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.defaultTemplateId = '1'
|
||||
const {container} = render(wrapDNDIntl(
|
||||
@@ -504,14 +495,14 @@ describe('components/centerPanel', () => {
|
||||
))
|
||||
const elementMenuWrapper = container.querySelector('.ButtonWithMenu > div.MenuWrapper')
|
||||
expect(elementMenuWrapper).not.toBeNull()
|
||||
userEvent.click(elementMenuWrapper!)
|
||||
await act(() => userEvent.click(elementMenuWrapper!))
|
||||
const elementCard1 = within(elementMenuWrapper!.parentElement!).getByRole('button', {name: 'card1'})
|
||||
expect(elementCard1).not.toBeNull()
|
||||
userEvent.click(elementCard1)
|
||||
await act(() => userEvent.click(elementCard1))
|
||||
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.defaultTemplateId = '1'
|
||||
const {container} = render(wrapDNDIntl(
|
||||
@@ -531,15 +522,15 @@ describe('components/centerPanel', () => {
|
||||
))
|
||||
const elementMenuWrapper = container.querySelector('.ButtonWithMenu > div.MenuWrapper')
|
||||
expect(elementMenuWrapper).not.toBeNull()
|
||||
userEvent.click(elementMenuWrapper!)
|
||||
await act(() => userEvent.click(elementMenuWrapper!))
|
||||
const elementCard1 = within(elementMenuWrapper!.parentElement!).getByRole('button', {name: 'card1'})
|
||||
expect(elementCard1).not.toBeNull()
|
||||
const elementMenuWrapperCard1 = within(elementCard1).getByRole('button', {name: 'menuwrapper'})
|
||||
expect(elementMenuWrapperCard1).not.toBeNull()
|
||||
userEvent.click(elementMenuWrapperCard1)
|
||||
await act(() => userEvent.click(elementMenuWrapperCard1))
|
||||
const elementEditMenuTemplate = within(elementMenuWrapperCard1).getByRole('button', {name: 'Edit'})
|
||||
expect(elementMenuWrapperCard1).not.toBeNull()
|
||||
userEvent.click(elementEditMenuTemplate)
|
||||
await act(() => userEvent.click(elementEditMenuTemplate))
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
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 ctaHandler = (e: React.MouseEvent) => {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import '@testing-library/jest-dom'
|
||||
import {render} from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import React from 'react'
|
||||
@@ -28,7 +27,7 @@ describe('/components/confirmAddUserForNotifications', () => {
|
||||
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 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)
|
||||
})
|
||||
|
||||
it('cancel button click runs onClose function', () => {
|
||||
it('cancel button click runs onClose function', async () => {
|
||||
const onClose = jest.fn()
|
||||
|
||||
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)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import '@testing-library/jest-dom'
|
||||
import {act, render} from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import React from 'react'
|
||||
@@ -55,16 +54,16 @@ describe('/components/confirmationDialogBox', () => {
|
||||
expect(containerWithCnfrmBtnText).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('confirm button click, run onConfirm Function once', () => {
|
||||
it('confirm button click, run onConfirm Function once', async () => {
|
||||
const result = render(
|
||||
wrapDNDIntl(<ConfirmationDialogBox dialogBox={dialogProps}/>),
|
||||
)
|
||||
|
||||
userEvent.click(result.getByTitle('Confirm'))
|
||||
await userEvent.click(result.getByTitle('Confirm'))
|
||||
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(
|
||||
wrapDNDIntl(
|
||||
<ConfirmationDialogBox
|
||||
@@ -73,21 +72,21 @@ describe('/components/confirmationDialogBox', () => {
|
||||
),
|
||||
)
|
||||
|
||||
userEvent.click(
|
||||
await userEvent.click(
|
||||
resultWithConfirmBtnText.getByTitle(dialogPropsWithCnfrmBtnText.confirmButtonText),
|
||||
)
|
||||
|
||||
expect(dialogPropsWithCnfrmBtnText.onConfirm).toBeCalledTimes(1)
|
||||
})
|
||||
|
||||
it('cancel button click runs onClose function', () => {
|
||||
it('cancel button click runs onClose function', async () => {
|
||||
const result = render(wrapDNDIntl(
|
||||
<ConfirmationDialogBox
|
||||
dialogBox={dialogProps}
|
||||
/>,
|
||||
))
|
||||
|
||||
userEvent.click(result.getByTitle('Cancel'))
|
||||
await userEvent.click(result.getByTitle('Cancel'))
|
||||
expect(dialogProps.onClose).toBeCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -15,7 +15,6 @@ exports[`components/content/checkboxElement should change title 1`] = `
|
||||
placeholder="Edit text..."
|
||||
spellcheck="true"
|
||||
title="new title"
|
||||
type="text"
|
||||
value="new title"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -16,7 +16,7 @@ import {IUser} from 'src/user'
|
||||
import AttachmentElement from './attachmentElement'
|
||||
|
||||
jest.mock('src/octoClient')
|
||||
const mockedOcto = mocked(octoClient, true)
|
||||
const mockedOcto = mocked(octoClient)
|
||||
mockedOcto.getFileAsDataUrl.mockResolvedValue({url: 'test.txt'})
|
||||
mockedOcto.getFileInfo.mockResolvedValue({
|
||||
name: 'test.txt',
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
screen,
|
||||
waitFor
|
||||
} from '@testing-library/react'
|
||||
import '@testing-library/jest-dom'
|
||||
import {mocked} from 'jest-mock'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
|
||||
@@ -21,7 +20,7 @@ import mutator from 'src/mutator'
|
||||
import CheckboxElement from './checkboxElement'
|
||||
|
||||
jest.mock('src/mutator')
|
||||
const mockedMutator = mocked(mutator, true)
|
||||
const mockedMutator = mocked(mutator)
|
||||
|
||||
const board = TestBlockFactory.createBoard()
|
||||
const card = TestBlockFactory.createCard(board)
|
||||
@@ -84,7 +83,7 @@ describe('components/content/checkboxElement', () => {
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('should change title', () => {
|
||||
it('should change title', async () => {
|
||||
const {container} = render(wrap(
|
||||
<CheckboxElement
|
||||
block={checkboxBlock}
|
||||
@@ -93,8 +92,8 @@ describe('components/content/checkboxElement', () => {
|
||||
))
|
||||
const newTitle = 'new title'
|
||||
const input = screen.getByRole('textbox', {name: /test-title/i})
|
||||
userEvent.clear(input)
|
||||
userEvent.type(input, newTitle)
|
||||
await userEvent.clear(input)
|
||||
await userEvent.type(input, newTitle)
|
||||
fireEvent.blur(input)
|
||||
expect(container).toMatchSnapshot()
|
||||
expect(mockedMutator.changeBlockTitle).toHaveBeenCalledTimes(1)
|
||||
@@ -106,7 +105,7 @@ describe('components/content/checkboxElement', () => {
|
||||
expect.anything())
|
||||
})
|
||||
|
||||
it('should toggle value', () => {
|
||||
it('should toggle value', async () => {
|
||||
const {container} = render(wrap(
|
||||
<CheckboxElement
|
||||
block={checkboxBlock}
|
||||
@@ -114,7 +113,7 @@ describe('components/content/checkboxElement', () => {
|
||||
/>,
|
||||
))
|
||||
const input = screen.getByRole('checkbox')
|
||||
userEvent.click(input)
|
||||
await userEvent.click(input)
|
||||
expect(container).toMatchSnapshot()
|
||||
expect(mockedMutator.updateBlock).toHaveBeenCalledTimes(1)
|
||||
expect(mockedMutator.updateBlock).toHaveBeenCalledWith(
|
||||
@@ -149,17 +148,17 @@ describe('components/content/checkboxElement', () => {
|
||||
const input = screen.getByRole('textbox', {name: /test-title/i})
|
||||
|
||||
// should not add new checkbox when current one has empty title
|
||||
userEvent.clear(input)
|
||||
userEvent.type(input, '{enter}')
|
||||
await userEvent.clear(input)
|
||||
await userEvent.type(input, '{Enter}')
|
||||
expect(addElement).toHaveBeenCalledTimes(0)
|
||||
|
||||
// should add new checkbox when current one has non-empty title
|
||||
userEvent.clear(input)
|
||||
userEvent.type(input, 'new-title{enter}')
|
||||
await userEvent.clear(input)
|
||||
await userEvent.type(input, 'new-title{Enter}')
|
||||
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)
|
||||
addedBlock.title = ''
|
||||
const deleteElement = jest.fn()
|
||||
@@ -173,15 +172,16 @@ describe('components/content/checkboxElement', () => {
|
||||
/>
|
||||
</CardDetailContext.Provider>,
|
||||
))
|
||||
|
||||
const input = screen.getByRole('textbox')
|
||||
userEvent.type(input, '{esc}')
|
||||
|
||||
// should delete if title is empty
|
||||
await userEvent.type(input, '{Escape}')
|
||||
expect(deleteElement).toHaveBeenCalledTimes(1)
|
||||
userEvent.type(input, '{enter}')
|
||||
await userEvent.type(input, '{Enter}')
|
||||
expect(deleteElement).toHaveBeenCalledTimes(2)
|
||||
|
||||
// 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)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// See LICENSE.txt for license information.
|
||||
import React, {ReactElement, ReactNode} from 'react'
|
||||
|
||||
import '@testing-library/jest-dom'
|
||||
|
||||
import {render} from '@testing-library/react'
|
||||
|
||||
@@ -54,6 +53,8 @@ describe('components/content/contentElement', () => {
|
||||
})
|
||||
|
||||
it('should return null for unknown type', () => {
|
||||
jest.spyOn(console, 'error').mockImplementation()
|
||||
|
||||
const block: ContentBlock = {...contentBlock, type: 'unknown'}
|
||||
const {container} = render(wrap(
|
||||
<ContentElement
|
||||
@@ -63,5 +64,6 @@ describe('components/content/contentElement', () => {
|
||||
/>,
|
||||
))
|
||||
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'
|
||||
|
||||
jest.mock('src/octoClient')
|
||||
const mockedOcto = mocked(octoClient, true)
|
||||
const mockedOcto = mocked(octoClient)
|
||||
mockedOcto.getFileAsDataUrl.mockResolvedValue({url: 'test.jpg'})
|
||||
|
||||
describe('components/content/ImageElement', () => {
|
||||
|
||||
@@ -5,7 +5,6 @@ import React from 'react'
|
||||
import {render, act} from '@testing-library/react'
|
||||
import {Provider as ReduxProvider} from 'react-redux'
|
||||
|
||||
import '@testing-library/jest-dom'
|
||||
|
||||
import {mocked} from 'jest-mock'
|
||||
|
||||
@@ -22,7 +21,7 @@ import TextElement from './textElement'
|
||||
jest.mock('src/utils')
|
||||
jest.mock('src/mutator')
|
||||
jest.mock('draft-js/lib/generateRandomKey', () => () => '123')
|
||||
const mockedUtils = mocked(Utils, true)
|
||||
const mockedUtils = mocked(Utils)
|
||||
mockedUtils.createGuid.mockReturnValue('test-id')
|
||||
const defaultBlock: TextBlock = {
|
||||
id: 'test-id',
|
||||
|
||||
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Ссылка в новой задаче
Block a user