Skip to content

Commit

Permalink
Enhances 'spo page section add'. Closes #1759
Browse files Browse the repository at this point in the history
  • Loading branch information
mkm17 authored and martinlingstuyl committed Sep 14, 2023
1 parent af1793c commit e414ac1
Show file tree
Hide file tree
Showing 5 changed files with 367 additions and 27 deletions.
26 changes: 25 additions & 1 deletion docs/docs/cmd/spo/page/page-section-add.mdx
Expand Up @@ -20,10 +20,16 @@ m365 spo page section add [options]
: URL of the site where the page to retrieve is located.

`-t, --sectionTemplate <sectionTemplate>`
: Type of section to add. Allowed values `OneColumn`, `OneColumnFullWidth`, `TwoColumn`, `ThreeColumn`, `TwoColumnLeft`, `TwoColumnRight`.
: Type of section to add. Allowed values `OneColumn`, `OneColumnFullWidth`, `TwoColumn`, `ThreeColumn`, `TwoColumnLeft`, `TwoColumnRight`, `Vertical`.

`--order [order]`
: Order of the section to add.

`--zoneEmphasis [zoneEmphasis]`
: Section background shading. Allowed values `None`, `Neutral`, `Soft`, `Strong`

`--isLayoutReflowOnTop`
: The position of the Vertical section for smaller screens. Applied only for Vertical section.
```

<Global />
Expand All @@ -40,6 +46,24 @@ Add section to the modern page
m365 spo page section add --pageName home.aspx --webUrl https://contoso.sharepoint.com/sites/newsletter --sectionTemplate OneColumn --order 1
```

Add section with background shading to the modern page

```sh
m365 spo page section add --pageName home.aspx --webUrl https://contoso.sharepoint.com/sites/newsletter --sectionTemplate OneColumn --zoneEmphasis Strong
```

Add a vertical section to the modern page. There can only be one vertical section per page. If a vertical section already exists on the page, the command will not have any effect.

```sh
m365 spo page section add --pageName home.aspx --webUrl https://contoso.sharepoint.com/sites/newsletter --sectionTemplate Vertical
```

Add Vertical section with background shading to the modern page with adjusting the position of this section for smaller screens to the top

```sh
m365 spo page section add --pageName home.aspx --webUrl https://contoso.sharepoint.com/sites/newsletter --sectionTemplate Vertical --zoneEmphasis Neutral --isLayoutReflowOnTop
```

## Response

The command won't return a response on success.
1 change: 1 addition & 0 deletions src/m365/spo/commands/page/canvasContent.ts
Expand Up @@ -16,4 +16,5 @@ interface ControlPosition {
sectionFactor: number;
sectionIndex: number;
zoneIndex: number;
isLayoutReflowOnTop?: boolean;
}
20 changes: 19 additions & 1 deletion src/m365/spo/commands/page/clientsidepages.ts
Expand Up @@ -32,7 +32,25 @@ export enum CanvasSectionTemplate {
/// <summary>
/// Two columns, left one is 1/3, right one 2/3
/// </summary>
TwoColumnRight
TwoColumnRight,
/// <summary>
/// Vertical
/// </summary>
Vertical
}

/**
* Section background shading
* 0 - None
* 1 - Neutral
* 2 - Soft
* 3 - Strong
*/
export enum ZoneEmphasis {
None = 0,
Neutral = 1,
Soft = 2,
Strong = 3
}

/**
Expand Down
260 changes: 255 additions & 5 deletions src/m365/spo/commands/page/page-section-add.spec.ts
Expand Up @@ -137,7 +137,7 @@ describe(commands.PAGE_SECTION_ADD, () => {
if ((opts.url as string).indexOf(`/_api/sitepages/pages/GetByUrl('sitepages/home.aspx')?$select=CanvasContent1,IsPageCheckedOutToCurrentUser`) > -1) {
return {
"IsPageCheckedOutToCurrentUser": true,
"CanvasContent1": null
"CanvasContent1": "[{\"controlType\":0,\"pageSettingsSlice\":{\"isDefaultDescription\":true,\"isDefaultThumbnail\":true}}]"
};
}

Expand Down Expand Up @@ -170,7 +170,7 @@ describe(commands.PAGE_SECTION_ADD, () => {
if ((opts.url as string).indexOf(`/_api/sitepages/pages/GetByUrl('sitepages/home.aspx')?$select=CanvasContent1,IsPageCheckedOutToCurrentUser`) > -1) {
return {
"IsPageCheckedOutToCurrentUser": true,
"CanvasContent1": null
"CanvasContent1": "[{\"controlType\":0,\"pageSettingsSlice\":{\"isDefaultDescription\":true,\"isDefaultThumbnail\":true}}]"
};
}

Expand Down Expand Up @@ -199,6 +199,39 @@ describe(commands.PAGE_SECTION_ADD, () => {
assert.strictEqual(data, JSON.stringify({ "CanvasContent1": "[{\"displayMode\":2,\"position\":{\"zoneIndex\":1,\"sectionIndex\":1,\"sectionFactor\":12,\"layoutIndex\":1},\"emphasis\":{}},{\"controlType\":0,\"pageSettingsSlice\":{\"isDefaultDescription\":true,\"isDefaultThumbnail\":true}}]" }));
});

it('adds a first section to an uncustomized page correctly even when CanvasContent1 of returned page is null', async () => {
sinon.stub(request, 'get').callsFake((opts) => {
if ((opts.url as string).indexOf(`/_api/sitepages/pages/GetByUrl('sitepages/home.aspx')?$select=CanvasContent1,IsPageCheckedOutToCurrentUser`) > -1) {
return Promise.resolve({
"IsPageCheckedOutToCurrentUser": true,
"CanvasContent1": null
});
}

return Promise.reject('Invalid request');
});

let data: string = '';
sinon.stub(request, 'post').callsFake((opts) => {
if ((opts.url as string).indexOf(`/_api/sitepages/pages/GetByUrl('sitepages/home.aspx')/savepage`) > -1) {
data = JSON.stringify(opts.data);
return Promise.resolve({});
}

return Promise.reject('Invalid request');
});

await command.action(logger, {
options:
{
pageName: 'home.aspx',
webUrl: 'https://contoso.sharepoint.com/sites/newsletter',
sectionTemplate: 'OneColumn'
}
});
assert.strictEqual(data, JSON.stringify({ "CanvasContent1": "[{\"displayMode\":2,\"position\":{\"zoneIndex\":1,\"sectionIndex\":1,\"sectionFactor\":12,\"layoutIndex\":1},\"emphasis\":{}},{\"controlType\":0,\"pageSettingsSlice\":{\"isDefaultDescription\":true,\"isDefaultThumbnail\":true}}]" }));
});

it('adds a first section to the page if no order specified', async () => {
sinon.stub(request, 'get').callsFake(async (opts) => {
if ((opts.url as string).indexOf(`/_api/sitepages/pages/GetByUrl('sitepages/home.aspx')?$select=CanvasContent1,IsPageCheckedOutToCurrentUser`) > -1) {
Expand Down Expand Up @@ -469,6 +502,109 @@ describe(commands.PAGE_SECTION_ADD, () => {
assert.strictEqual(data, JSON.stringify({ "CanvasContent1": "[{\"displayMode\":2,\"position\":{\"zoneIndex\":0.5,\"sectionIndex\":1,\"sectionFactor\":6,\"layoutIndex\":1},\"emphasis\":{}},{\"displayMode\":2,\"position\":{\"zoneIndex\":0.5,\"sectionIndex\":2,\"sectionFactor\":6,\"layoutIndex\":1},\"emphasis\":{}},{\"displayMode\":2,\"position\":{\"zoneIndex\":0.75,\"sectionIndex\":1,\"sectionFactor\":6,\"layoutIndex\":1},\"emphasis\":{}},{\"displayMode\":2,\"position\":{\"zoneIndex\":0.75,\"sectionIndex\":2,\"sectionFactor\":6,\"layoutIndex\":1},\"emphasis\":{}},{\"displayMode\":2,\"position\":{\"zoneIndex\":1,\"sectionIndex\":1,\"sectionFactor\":12,\"layoutIndex\":1},\"emphasis\":{}},{\"displayMode\":2,\"position\":{\"zoneIndex\":1.5,\"sectionIndex\":1,\"sectionFactor\":4,\"layoutIndex\":1},\"emphasis\":{}},{\"displayMode\":2,\"position\":{\"zoneIndex\":1.5,\"sectionIndex\":2,\"sectionFactor\":4,\"layoutIndex\":1},\"emphasis\":{}},{\"displayMode\":2,\"position\":{\"zoneIndex\":1.5,\"sectionIndex\":3,\"sectionFactor\":4,\"layoutIndex\":1},\"emphasis\":{}},{\"displayMode\":2,\"position\":{\"zoneIndex\":2,\"sectionIndex\":1,\"sectionFactor\":4,\"layoutIndex\":1},\"emphasis\":{}},{\"displayMode\":2,\"position\":{\"zoneIndex\":2,\"sectionIndex\":2,\"sectionFactor\":8,\"layoutIndex\":1},\"emphasis\":{}},{\"controlType\":0,\"pageSettingsSlice\":{\"isDefaultDescription\":true,\"isDefaultThumbnail\":true}}]" }));
});

it('adds a Vertical section at the end to an uncustomized page', async () => {
sinon.stub(request, 'get').callsFake((opts) => {
if ((opts.url as string).indexOf(`/_api/sitepages/pages/GetByUrl('sitepages/home.aspx')?$select=CanvasContent1,IsPageCheckedOutToCurrentUser`) > -1) {
return Promise.resolve({
"IsPageCheckedOutToCurrentUser": true,
"CanvasContent1": "[{\"controlType\":0,\"pageSettingsSlice\":{\"isDefaultDescription\":true,\"isDefaultThumbnail\":true}}]"
});
}

return Promise.reject('Invalid request');
});

let data: string = '';
sinon.stub(request, 'post').callsFake((opts) => {
if ((opts.url as string).indexOf(`/_api/sitepages/pages/GetByUrl('sitepages/home.aspx')/savepage`) > -1) {
data = JSON.stringify(opts.data);
return Promise.resolve({});
}

return Promise.reject('Invalid request');
});

await command.action(logger, {
options:
{
pageName: 'home.aspx',
webUrl: 'https://contoso.sharepoint.com/sites/newsletter',
sectionTemplate: 'Vertical'
}
});
assert.strictEqual(data, JSON.stringify({ "CanvasContent1": "[{\"displayMode\":2,\"position\":{\"zoneIndex\":1,\"sectionIndex\":1,\"sectionFactor\":12,\"layoutIndex\":2,\"isLayoutReflowOnTop\":false,\"controlIndex\":1},\"emphasis\":{}},{\"controlType\":0,\"pageSettingsSlice\":{\"isDefaultDescription\":true,\"isDefaultThumbnail\":true}}]" }));
});


it('adds a Vertical section at the end with correct zoneEmphasisValue to an uncustomized page', async () => {
sinon.stub(request, 'get').callsFake((opts) => {
if ((opts.url as string).indexOf(`/_api/sitepages/pages/GetByUrl('sitepages/home.aspx')?$select=CanvasContent1,IsPageCheckedOutToCurrentUser`) > -1) {
return Promise.resolve({
"IsPageCheckedOutToCurrentUser": true,
"CanvasContent1": "[{\"controlType\":0,\"pageSettingsSlice\":{\"isDefaultDescription\":true,\"isDefaultThumbnail\":true}}]"
});
}

return Promise.reject('Invalid request');
});

let data: string = '';
sinon.stub(request, 'post').callsFake((opts) => {
if ((opts.url as string).indexOf(`/_api/sitepages/pages/GetByUrl('sitepages/home.aspx')/savepage`) > -1) {
data = JSON.stringify(opts.data);
return Promise.resolve({});
}

return Promise.reject('Invalid request');
});

await command.action(logger, {
options:
{
pageName: 'home.aspx',
webUrl: 'https://contoso.sharepoint.com/sites/newsletter',
sectionTemplate: 'Vertical',
zoneEmphasis: 'Neutral'
}
});
assert.strictEqual(data, JSON.stringify({ "CanvasContent1": "[{\"displayMode\":2,\"position\":{\"zoneIndex\":1,\"sectionIndex\":1,\"sectionFactor\":12,\"layoutIndex\":2,\"isLayoutReflowOnTop\":false,\"controlIndex\":1},\"emphasis\":{\"zoneEmphasis\":1}},{\"controlType\":0,\"pageSettingsSlice\":{\"isDefaultDescription\":true,\"isDefaultThumbnail\":true}}]" }));
});

it('adds a Vertical section at the end with correct zoneEmphasisValue and isLayoutReflowOnTop values to an uncustomized page', async () => {
sinon.stub(request, 'get').callsFake((opts) => {
if ((opts.url as string).indexOf(`/_api/sitepages/pages/GetByUrl('sitepages/home.aspx')?$select=CanvasContent1,IsPageCheckedOutToCurrentUser`) > -1) {
return Promise.resolve({
"IsPageCheckedOutToCurrentUser": true,
"CanvasContent1": "[{\"controlType\":0,\"pageSettingsSlice\":{\"isDefaultDescription\":true,\"isDefaultThumbnail\":true}}]"
});
}

return Promise.reject('Invalid request');
});

let data: string = '';
sinon.stub(request, 'post').callsFake((opts) => {
if ((opts.url as string).indexOf(`/_api/sitepages/pages/GetByUrl('sitepages/home.aspx')/savepage`) > -1) {
data = JSON.stringify(opts.data);
return Promise.resolve({});
}

return Promise.reject('Invalid request');
});

await command.action(logger, {
options:
{
pageName: 'home.aspx',
webUrl: 'https://contoso.sharepoint.com/sites/newsletter',
sectionTemplate: 'Vertical',
zoneEmphasis: 'Neutral',
isLayoutReflowOnTop: true
}
});
assert.strictEqual(data, JSON.stringify({ "CanvasContent1": "[{\"displayMode\":2,\"position\":{\"zoneIndex\":1,\"sectionIndex\":1,\"sectionFactor\":12,\"layoutIndex\":2,\"isLayoutReflowOnTop\":true,\"controlIndex\":1},\"emphasis\":{\"zoneEmphasis\":1}},{\"controlType\":0,\"pageSettingsSlice\":{\"isDefaultDescription\":true,\"isDefaultThumbnail\":true}}]" }));
});

it('correctly handles random API error', async () => {
sinon.stub(request, 'get').callsFake(() => {
throw 'An error has occurred';
Expand Down Expand Up @@ -533,19 +669,59 @@ describe(commands.PAGE_SECTION_ADD, () => {
assert.notStrictEqual(actual, true);
});

it('passes validation if all the parameters are specified', async () => {
it('fails validation if zoneEmphasis is not valid', async () => {
const actual = await command.validate({
options: {
pageName: 'page.aspx',
webUrl: 'https://contoso.sharepoint.com',
sectionTemplate: 'OneColumn',
zoneEmphasis: 'Invalid'
}
}, commandInfo);
assert.notStrictEqual(actual, true);
});


it('fails validation if isLayoutReflowOnTop is valid but sectionTemplate is not Vertical', async () => {
const actual = await command.validate({
options: {
pageName: 'page.aspx',
webUrl: 'https://contoso.sharepoint.com',
sectionTemplate: 'OneColumn',
isLayoutReflowOnTop: true
}
}, commandInfo);
assert.notStrictEqual(actual, true);
});

it('passes validation if all the parameters are specified for a regular Section', async () => {
const actual = await command.validate({
options: {
order: 1,
sectionTemplate: 'OneColumn',
webUrl: 'https://contoso.sharepoint.com',
pageName: 'Home.aspx'
pageName: 'Home.aspx',
zoneEmphasis: 'None'
}
}, commandInfo);
assert.strictEqual(actual, true);
});

it('passes validation if order is not specified', async () => {
it('passes validation if all the parameters are specified for Vertical Section', async () => {
const actual = await command.validate({
options: {
order: 1,
sectionTemplate: 'Vertical',
webUrl: 'https://contoso.sharepoint.com',
pageName: 'Home.aspx',
zoneEmphasis: 'None',
isLayoutReflowOnTop: false
}
}, commandInfo);
assert.strictEqual(actual, true);
});

it('passes validation if order, zoneEmphasis and isLayoutReflowOnTop are not specified', async () => {
const actual = await command.validate({
options: {
sectionTemplate: 'OneColumn',
Expand All @@ -556,6 +732,58 @@ describe(commands.PAGE_SECTION_ADD, () => {
assert.strictEqual(actual, true);
});

it('passes validation if order and isLayoutReflowOnTop are not specified', async () => {
const actual = await command.validate({
options: {
sectionTemplate: 'OneColumn',
webUrl: 'https://contoso.sharepoint.com',
pageName: 'Home.aspx',
zoneEmphasis: 'None'
}
}, commandInfo);
assert.strictEqual(actual, true);
});

it('passes validation if isLayoutReflowOnTop is specified along with Vertical sectionTemplate', async () => {
const actual = await command.validate({
options: {
sectionTemplate: 'Vertical',
webUrl: 'https://contoso.sharepoint.com',
pageName: 'Home.aspx',
zoneEmphasis: 'None',
order: 1,
isLayoutReflowOnTop: false
}
}, commandInfo);
assert.strictEqual(actual, true);
});

it('passes validation if order is not specified', async () => {
const actual = await command.validate({
options: {
sectionTemplate: 'Vertical',
webUrl: 'https://contoso.sharepoint.com',
pageName: 'Home.aspx',
isLayoutReflowOnTop: false
}
}, commandInfo);
assert.strictEqual(actual, true);
});

it('passes validation if zoneEmphasis is not specified', async () => {
const actual = await command.validate({
options: {
sectionTemplate: 'Vertical',
webUrl: 'https://contoso.sharepoint.com',
pageName: 'Home.aspx',
order: 1,
isLayoutReflowOnTop: false
}
}, commandInfo);
assert.strictEqual(actual, true);
});


it('supports specifying page name', () => {
const options = command.options;
let containsOption = false;
Expand Down Expand Up @@ -599,4 +827,26 @@ describe(commands.PAGE_SECTION_ADD, () => {
});
assert(containsOption);
});

it('supports specifying zoneEmphasis', () => {
const options = command.options;
let containsOption = false;
options.forEach((o) => {
if (o.option.indexOf('--zoneEmphasis') > -1) {
containsOption = true;
}
});
assert(containsOption);
});

it('supports specifying isLayoutReflowOnTop', () => {
const options = command.options;
let containsOption = false;
options.forEach((o) => {
if (o.option.indexOf('--isLayoutReflowOnTop') > -1) {
containsOption = true;
}
});
assert(containsOption);
});
});

0 comments on commit e414ac1

Please sign in to comment.