diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7e20d38..159ec06 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -29,6 +29,7 @@ jobs: TEST_DATABASE_ID: ${{ secrets.TEST_DATABASE_ID }} TEST_PAGE_ID: ${{ secrets.TEST_PAGE_ID }} TEST_BLOCK_ID: ${{ secrets.TEST_BLOCK_ID }} + TEST_BLOCK_HEADING_ID: ${{ secrets.TEST_BLOCK_HEADING_ID }} EXEC_ENV: 'github_actions' - name: Format coverage diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fa9e33..f2e58bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,4 +77,32 @@ > Release date: 05/Jul/2021 * Fix warnings for documentation * Improve documentation -* Add contribution rules \ No newline at end of file +* Add contribution rules + +## v1.1.0: +> Release date: 10/Jul/2021 +* Add more blocks support for `(PATCH): block children` endpoint + * `BulletedListItem` block + * `NumberedListItem` block + * `Toggle` block +* Add `children` field for blocks: + * `BulletedListItem` + * `NumberedListItem` + * `ToDo` + * `Toggle` + * `Paragraph` +* Add methods to manipulate `content` and `children` for blocks: + * `addText(String text, {TextAnnotations? annotations})` + * `addChild(Block block)` + * `addChildren(List blocks)` +* Add `Children.withBlocks(List blocks)` constructor +* Add `final` for `type` fields to not allow overwrite: + * Objects + * Blocks +* Add `BaseClient` class to avoid duplicated code for clients +* Add `deprecated` annotations for future changes: + * Remove `textSeparation` parameter/field + * Remove `add(Text text)` function + * Remove `texts` getter for `Paragraph` + * Remove named parameters for `Children` class +* Update documentation \ No newline at end of file diff --git a/README.md b/README.md index d000a69..b082d18 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,6 @@ See the [ROADMAP](ROADMAP.md) file to see what is coming next. - [Tests](#tests) - [Example:](#example) - [Next release](#next-release) - - [v1.1.0:](#v110) # Usage **Important**: The methods return a `NotionResponse`. You can find how to use it in its [documentation][1]. @@ -47,7 +46,7 @@ _To see more examples [go here](https://github.com/jonathangomz/notion_api/blob/ ### Append blocks children ```dart // Create children instance: -Children children = Children().addAll([ +Children children = Children.withBlocks([ Heading(text: Text('Test')), Paragraph(texts: [ Text('Lorem ipsum (A)'), @@ -98,6 +97,7 @@ To be able to run the tests you will have to have a `.env` file on the root dire * TEST_DATABASE_ID: The database id where you will be working on. * TEST_PAGE_ID: Some page id inside the database specified above. * TEST_BLOCK_ID: Some block id inside the page specified above. +* TEST_BLOCK_HEADING_ID: Some heading block id inside the page specified above. ### Example: _The values are not valid of course._ @@ -106,19 +106,10 @@ TOKEN=secret_Oa24V8FbJ49JluJankVOQihyLiMXwqSQeeHuSFobQDW TEST_DATABASE_ID=366da3d646bb458128071fdb2fbbf427 TEST_PAGE_ID=c3b53019-4470-443b-a141-95a3a1a44g60 TEST_BLOCK_ID=c8hac4bb32af48889228bf483d938e34 +TEST_BLOCK_HEADING_ID=c8hac4bb32af48889228bf483d938e34 ``` # Next release -## v1.1.0: -> Release date: 10/Jul/2021 -* Add more blocks for `(PATCH): block children` endpoint - * `BulletedList` block - * `NumberedList` block - * `Toggle` block -* Add `Children.with(List blocks)` constructor -* Add singleton (_if possible_) -* Add `final` for override types to not allow change the field: - * Objects - * Blocks +I don't know yet. If you have suggestions feel free to create an Issue or to create a PR with the feature. [1]:https://pub.dev/documentation/notion_api/1.0.0-beta1/responses_notion_response/NotionResponse-class.html \ No newline at end of file diff --git a/ROADMAP.md b/ROADMAP.md index 39ecce5..f7ceaaa 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,18 +1,35 @@ # Roadmap ## More coming soon... +I don't know yet. If you have suggestions feel free to create an Issue or to create a PR with the feature. -## v1.1.0: +## v1.1.0: ✅ > Release date: 10/Jul/2021 -* Add more blocks for `(PATCH): block children` endpoint - * `BulletedList` block - * `NumberedList` block +* Add more blocks support for `(PATCH): block children` endpoint + * `BulletedListItem` block + * `NumberedListItem` block * `Toggle` block -* Add `Children.with(List blocks)` constructor -* Add singleton (_if possible_) -* Add `final` for override types to not allow change the field: - * Objects - * Blocks +* Add `children` field for blocks: + * `BulletedListItem` + * `NumberedListItem` + * `ToDo` + * `Toggle` + * `Paragraph` +* Add methods to manipulate `content` and `children` for blocks: + * `addText(String text, {TextAnnotations? annotations})` + * `addChild(Block block)` + * `addChildren(List blocks)` +* Add `Children.withBlocks(List blocks)` constructor +* Add `final` for `type` fields to not allow overwrite: + * Objects + * Blocks +* Add `BaseClient` class to avoid duplicated code for clients +* Add `deprecated` annotations for future changes: + * Remove `textSeparation` parameter/field + * Remove `add(Text text)` function + * Remove `texts` getter for `Paragraph` + * Remove named parameters for `Children` class +* Update documentation ## v1.0.2: ✅ > Release date: 05/Jul/2021 diff --git a/example/example.md b/example/example.md index 4fb6751..e4f7048 100644 --- a/example/example.md +++ b/example/example.md @@ -11,13 +11,11 @@ - [Block children](#block-children) - [Retrieve block children](#retrieve-block-children) - [Append block children](#append-block-children) - - [Example](#example) - - [Heading & Paragraph](#heading--paragraph) - - [Code](#code) - - [Result](#result) - - [To do](#to-do) - - [Code](#code-1) - - [Result](#result-1) + - [Heading & Paragraph](#heading--paragraph) + - [To do](#to-do) + - [Toggle](#toggle) + - [Bulleted List Item](#bulleted-list-item) + - [Numbered List Item](#numbered-list-item) # Initialization ## Full instance @@ -99,12 +97,11 @@ _Parameters:_ - The `Paragraph` object can contain only `Text` objects. - `Text` can receive a `TextAnnotations` class with the style of the text. -### Example -#### Heading & Paragraph -##### Code +### Heading & Paragraph +**Code** ```dart // Create children instance: -// * Old way +// * Deprecated way // Children oldWay = Children( // heading: Heading('Test'), // paragraph: Paragraph( @@ -127,13 +124,17 @@ Children childrenA = Children().addAll([ Heading(text: Text('Test')), Paragraph(texts: [ Text('Lorem ipsum (A)'), - Text('Lorem ipsum (B)', - annotations: TextAnnotations( - bold: true, - underline: true, - color: ColorsTypes.Orange, - )) - ]) + Text( + 'Lorem ipsum (B)', + annotations: TextAnnotations( + bold: true, + underline: true, + color: ColorsTypes.Orange, + ), + ), + ], children: [ + Heading(text: Text('Subtitle'), type: 3), + ]), ]); // * New way using single `add()` @@ -141,28 +142,51 @@ Children childrenB = Children().add(Heading(text: Text('Test'))).add(Paragraph(texts: [ Text('Lorem ipsum (A)'), Text('Lorem ipsum (B)', - annotations: TextAnnotations( - bold: true, - underline: true, - color: ColorsTypes.Orange, - )) - ])); + annotations: TextAnnotations( + bold: true, + underline: true, + color: ColorsTypes.Orange, + ), + ), + ], children: [ + Heading(text: Text('Subtitle'), type: 3), + ], +)); + +// * New way using `withBlocks()` constructor +Children childrenC = Children.withBlocks([ + Heading(text: Text('Test')), + Paragraph(texts: [ + Text('Lorem ipsum (A)'), + Text( + 'Lorem ipsum (B)', + annotations: TextAnnotations( + bold: true, + underline: true, + color: ColorsTypes.Orange, + ), + ), + ], children: [ + Heading(text: Text('Subtitle'), type: 3), + ]), +]); // Send the instance to Notion notion.blocks.append( to: 'YOUR_BLOCK_ID', - children: childrenB, // or `childrenA`, both are the same. + children: childrenA, // or `childrenB` or `childrenC`, any of these will produce the same result. ); ``` -##### Result -![heading¶graph](https://raw.githubusercontent.com/jonathangomz/notion_api/main/example/images/heading_paragraph.png) +**Result** + +![heading¶graph](https://raw.githubusercontent.com/jonathangomz/notion_api/main/example/images/headingAndParagraph.png) -#### To do -##### Code +### To do +**Code** ```dart // Create children instance: -// * Old way +// * Deprecated way // Children children = // Children( // toDo: [ @@ -181,7 +205,7 @@ notion.blocks.append( // // * New way Children children = - Children().addAll([ + Children.withBlocks([ ToDo(text: Text('This is a todo item A')), ToDo( texts: [ @@ -192,6 +216,10 @@ Children children = ), ], ), + ToDo(text: Text('Todo item with children'), children: [ + BulletedListItem(text: Text('A')), + BulletedListItem(text: Text('B')), + ]), ], ); @@ -202,7 +230,96 @@ notion.blocks.append( ); ``` -##### Result +**Result** ![todo](https://raw.githubusercontent.com/jonathangomz/notion_api/main/example/images/todo.png) +### Toggle +**Code** +```dart +Children children = + Children.withBlocks([ + Toggle( + text: Text('This is a toggle block'), + children: [ + Paragraph( + texts: [ + Text( + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas venenatis dolor sed ex egestas, et vehicula tellus faucibus. Sed pellentesque tellus eget imperdiet vulputate.') + ], + ), + BulletedListItem(text: Text('A')), + BulletedListItem(text: Text('B')), + BulletedListItem(text: Text('B')), + ], + ), + ], +); + +// Send the instance to Notion +notion.blocks.append( + to: 'YOUR_BLOCK_ID', + children: children, +); +``` +**Result** +![toggle](https://raw.githubusercontent.com/jonathangomz/notion_api/main/example/images/toggle.png) + +### Bulleted List Item +**Code** +```dart +Children children = + Children.withBlocks([ + BulletedListItem(text: Text('This is a bulleted list item A')), + BulletedListItem(text: Text('This is a bulleted list item B')), + BulletedListItem( + text: Text('This is a bulleted list item with children'), + children: [ + Paragraph(texts: [ + Text('A'), + Text('B'), + Text('C'), + ]) + ], + ), + ], +); + +// Send the instance to Notion +notion.blocks.append( + to: 'YOUR_BLOCK_ID', + children: children, +); +``` +**Result** +![bulletedListItem](https://raw.githubusercontent.com/jonathangomz/notion_api/main/example/images/bulletedListItem.png) + +### Numbered List Item +**Code** +```dart +Children children = + Children.withBlocks([ + NumberedListItem(text: Text('This is a numbered list item A')), + NumberedListItem(text: Text('This is a numbered list item B')), + NumberedListItem( + text: Text('This is a bulleted list item with children'), + children: [ + Paragraph(texts: [ + Text('A'), + Text('B'), + Text('C'), + ]) + ], + ), + ], +); + +// Send the instance to Notion +notion.blocks.append( + to: 'YOUR_BLOCK_ID', + children: children, +); +``` +**Result** +![numberedListItem](https://raw.githubusercontent.com/jonathangomz/notion_api/main/example/images/numberedListItem.png) + [1]: https://developers.notion.com/reference/get-databases \ No newline at end of file diff --git a/example/images/bulletedListItem.png b/example/images/bulletedListItem.png new file mode 100644 index 0000000..f6bb3c6 Binary files /dev/null and b/example/images/bulletedListItem.png differ diff --git a/example/images/headingAndParagraph.png b/example/images/headingAndParagraph.png new file mode 100644 index 0000000..c5f4e69 Binary files /dev/null and b/example/images/headingAndParagraph.png differ diff --git a/example/images/numberedListItem.png b/example/images/numberedListItem.png new file mode 100644 index 0000000..d50e7bf Binary files /dev/null and b/example/images/numberedListItem.png differ diff --git a/example/images/todo.png b/example/images/todo.png index 5be787c..8b5fc46 100644 Binary files a/example/images/todo.png and b/example/images/todo.png differ diff --git a/example/images/toggle.png b/example/images/toggle.png new file mode 100644 index 0000000..e8b1b35 Binary files /dev/null and b/example/images/toggle.png differ diff --git a/lib/base_client.dart b/lib/base_client.dart new file mode 100644 index 0000000..4b8afe2 --- /dev/null +++ b/lib/base_client.dart @@ -0,0 +1,25 @@ +import 'package:notion_api/statics.dart'; + +abstract class BaseClient { + /// The API integration secret token. + String token; + + /// The API version. + String v; + + /// The API date version. + /// + /// It's not the same as the API version. + String dateVersion; + + /// The path of the requests group. + String path = ''; + + BaseClient({ + required String token, + String version: latestVersion, + String dateVersion: latestDateVersion, + }) : this.token = token, + this.v = version, + this.dateVersion = dateVersion; +} diff --git a/lib/notion/blocks/block.dart b/lib/notion/blocks/block.dart index 335ab03..152f90f 100644 --- a/lib/notion/blocks/block.dart +++ b/lib/notion/blocks/block.dart @@ -5,7 +5,8 @@ import 'package:notion_api/utils/utils.dart'; /// A base representation of any Notion block object. class Block extends BaseFields { /// The type of object. Always Block for this. - ObjectTypes object = ObjectTypes.Block; + @override + final ObjectTypes object = ObjectTypes.Block; /// The block id. String id = ''; @@ -38,10 +39,10 @@ class Block extends BaseFields { bool get isToogle => this.type == BlockTypes.Toggle; /// Returns true if is a Bulleted block. - bool get isBulleted => this.type == BlockTypes.BulletedList; + bool get isBulletedItem => this.type == BlockTypes.BulletedListItem; /// Returns true if is a Numbered block. - bool get isNumbered => this.type == BlockTypes.NumberedList; + bool get isNumberedItem => this.type == BlockTypes.NumberedListItem; /// Returns true if is a Child block. bool get isChild => this.type == BlockTypes.Child; diff --git a/lib/notion/blocks/bulleted_list_item.dart b/lib/notion/blocks/bulleted_list_item.dart new file mode 100644 index 0000000..5288aad --- /dev/null +++ b/lib/notion/blocks/bulleted_list_item.dart @@ -0,0 +1,63 @@ +import 'package:notion_api/notion/blocks/block.dart'; +import 'package:notion_api/notion/general/rich_text.dart'; +import 'package:notion_api/notion/general/types/notion_types.dart'; + +/// A representation of the Bulleted List Item Notion block object; +class BulletedListItem extends Block { + /// The block type. Always BulletedListItem. + @override + final BlockTypes type = BlockTypes.BulletedListItem; + + List _content = []; + List _children = []; + + /// The content of this block. + List get content => _content.toList(); + + /// The children of this block. + List get children => _children.toList(); + + /// Main bulleted list item constructor. + /// + /// Can receive a single [text] or a list of [texts]. If both are included also both fields are added to the heading content adding first the [text] field. Also can receive the [children] of the block. + BulletedListItem({ + Text? text, + List texts: const [], + List children: const [], + }) { + if (text != null) { + _content.add(text); + } + _content.addAll(texts); + _children.addAll(children); + } + + /// Add a [text] to the rich text array and returns this instance. Also can receive the [annotations] of the text. + BulletedListItem addText(String text, {TextAnnotations? annotations}) { + this._content.add(Text(text, annotations: annotations)); + return this; + } + + /// Add a new [block] to the children and returns this instance. + BulletedListItem addChild(Block block) { + this._children.add(block); + return this; + } + + /// Add a list of [blocks] to the children and returns this instance. + BulletedListItem addChildren(List blocks) { + this._children.addAll(blocks); + return this; + } + + /// Convert this to a valid json representation for the Notion API. + @override + Map toJson() => { + 'object': strObject, + 'type': strType, + strType: { + 'text': _content.map((e) => e.toJson()).toList(), + 'children': _children.map((e) => e.toJson()).toList(), + }, + }; +} diff --git a/lib/notion/blocks/heading.dart b/lib/notion/blocks/heading.dart index d840ae0..03fa8dd 100644 --- a/lib/notion/blocks/heading.dart +++ b/lib/notion/blocks/heading.dart @@ -44,11 +44,18 @@ class Heading extends Block { } /// Add a new [text] to the paragraph content and returns this instance. + @Deprecated('Use `addText(Block)` instead') Heading add(Text text) { this._content.add(text); return this; } + /// Add a [text] to the rich text array and returns this instance. Also can receive the [annotations] of the text. + Heading addText(String text, {TextAnnotations? annotations}) { + this._content.add(Text(text, annotations: annotations)); + return this; + } + /// Convert this to a valid json representation for the Notion API. @override Map toJson() => { diff --git a/lib/notion/blocks/numbered_list_item.dart b/lib/notion/blocks/numbered_list_item.dart new file mode 100644 index 0000000..7dc93c8 --- /dev/null +++ b/lib/notion/blocks/numbered_list_item.dart @@ -0,0 +1,63 @@ +import 'package:notion_api/notion/blocks/block.dart'; +import 'package:notion_api/notion/general/rich_text.dart'; +import 'package:notion_api/notion/general/types/notion_types.dart'; + +/// A representation of the Bulleted List Item Notion block object; +class NumberedListItem extends Block { + /// The block type. Always NumberedListItem. + @override + final BlockTypes type = BlockTypes.NumberedListItem; + + List _content = []; + List _children = []; + + /// The content of this block. + List get content => _content.toList(); + + /// The children of this block. + List get children => _children.toList(); + + /// Main numbered list item constructor. + /// + /// Can receive a single [text] or a list of [texts]. If both are included also both fields are added to the heading content adding first the [text] field. Also can receive the [children] of the block. + NumberedListItem({ + Text? text, + List texts: const [], + List children: const [], + }) { + if (text != null) { + _content.add(text); + } + _content.addAll(texts); + _children.addAll(children); + } + + /// Add a [text] to the rich text array and returns this instance. Also can receive the [annotations] of the text. + NumberedListItem addText(String text, {TextAnnotations? annotations}) { + this._content.add(Text(text, annotations: annotations)); + return this; + } + + /// Add a new [block] to the children and returns this instance. + NumberedListItem addChild(Block block) { + this._children.add(block); + return this; + } + + /// Add a list of [blocks] to the children and returns this instance. + NumberedListItem addChildren(List blocks) { + this._children.addAll(blocks); + return this; + } + + /// Convert this to a valid json representation for the Notion API. + @override + Map toJson() => { + 'object': strObject, + 'type': strType, + strType: { + 'text': _content.map((e) => e.toJson()).toList(), + 'children': _children.map((e) => e.toJson()).toList(), + }, + }; +} diff --git a/lib/notion/blocks/paragraph.dart b/lib/notion/blocks/paragraph.dart index 6197035..a3c0e97 100644 --- a/lib/notion/blocks/paragraph.dart +++ b/lib/notion/blocks/paragraph.dart @@ -6,40 +6,70 @@ import 'package:notion_api/notion/general/rich_text.dart'; class Paragraph extends Block { /// The block type. Always Paragraph for this. @override - BlockTypes type = BlockTypes.Paragraph; + final BlockTypes type = BlockTypes.Paragraph; List _content = []; + List _children = []; /// The separator for the Text objects. + @Deprecated('Text separation will be by your own') String textSeparator; /// The content of this block. + @Deprecated('Instead use `content`') List get texts => _content.toList(); + /// The content of this block. + List get content => _content.toList(); + + /// The children of this block. + List get children => _children.toList(); + /// Main paragraph constructor. /// - /// Can receive a single [text] or a list of [texts]. If both are included also both fields are added to the heading content adding first the [text] field. + /// Can receive a single [text] or a list of [texts]. If both are included also both fields are added to the heading content adding first the [text] field. Also can receive the [children] of the block. + /// + /// _Deprecated:_ [textSeparator] will be removed and the separation will be by your own. This because that's the same way that `Text` & `RichText` works on Flutter. In this way you can add annotations for a part of a word instead of only full words or phrases. /// /// Also a [textSeparator] can be anexed to separate the texts on the json generated using the `toJson()` function. The separator is used because when the text is displayed is all together without any kind of separation and adding the separator that behavior is avoided. By default the [textSeparator] is an space (" "). Paragraph({ Text? text, - List? texts, - this.textSeparator: ' ', + List texts: const [], + List children: const [], + @deprecated this.textSeparator: ' ', }) { if (text != null) { this._content.add(text); } - if (texts != null) { - this._content.addAll(texts); - } + this._content.addAll(texts); + this._children.addAll(children); } /// Add a new [text] to the paragraph content and returns this instance. + @Deprecated('Use `addText(Block)` instead') Paragraph add(Text text) { this._content.add(text); return this; } + /// Add a [text] to the rich text array and returns this instance. Also can receive the [annotations] of the text. + Paragraph addText(String text, {TextAnnotations? annotations}) { + this._content.add(Text(text, annotations: annotations)); + return this; + } + + /// Add a new [block] to the children and returns this instance. + Paragraph addChild(Block block) { + this._children.add(block); + return this; + } + + /// Add a list of [blocks] to the children and returns this instance. + Paragraph addChildren(List blocks) { + this._children.addAll(blocks); + return this; + } + /// Convert this to a valid json representation for the Notion API. @override Map toJson() => { @@ -48,7 +78,8 @@ class Paragraph extends Block { strType: { 'text': _content .map((e) => e.toJson(textSeparator: textSeparator)) - .toList() + .toList(), + 'children': _children.map((e) => e.toJson()).toList(), } }; } diff --git a/lib/notion/blocks/todo.dart b/lib/notion/blocks/todo.dart index b399ffe..056696e 100644 --- a/lib/notion/blocks/todo.dart +++ b/lib/notion/blocks/todo.dart @@ -6,11 +6,13 @@ import 'package:notion_api/notion/general/rich_text.dart'; class ToDo extends Block { /// The block type. Always ToDo for this. @override - BlockTypes type = BlockTypes.ToDo; + final BlockTypes type = BlockTypes.ToDo; List _content = []; + List _children = []; /// The separator for the Text objects. + @Deprecated('Text separation will be by your own') String textSeparator; /// The checked value. @@ -19,24 +21,30 @@ class ToDo extends Block { /// The content of this block. List get content => _content.toList(); + /// The children of this block. + List get children => _children.toList(); + /// Main to do constructor. /// - /// Can receive a single [text] or a list of [texts]. If both are included also both fields are added to the heading content adding first the [text] field. - /// - /// Also a [textSeparator] can be anexed to separate the texts on the json generated using the `toJson()` function. The separator is used because when the text is displayed is all together without any kind of separation and adding the separator that behavior is avoided. By default the [textSeparator] is an space (" "). + /// Can receive a single [text] or a list of [texts]. If both are included also both fields are added to the heading content adding first the [text] field. Also can receive the [children] of the block. /// /// The [checked] field define if the To do option is marked as done. By default is false. - ToDo( - {Text? text, - List? texts, - this.textSeparator: ' ', - this.checked: false}) { + /// + /// _Deprecated:_ [textSeparator] will be removed and the separation will be by your own. This because that's the same way that `Text` & `RichText` works on Flutter. In this way you can add annotations for a part of a word instead of only full words or phrases. + /// + /// Also a [textSeparator] can be anexed to separate the texts on the json generated using the `toJson()` function. The separator is used because when the text is displayed is all together without any kind of separation and adding the separator that behavior is avoided. By default the [textSeparator] is an space (" "). + ToDo({ + Text? text, + List texts: const [], + List children: const [], + this.checked: false, + @deprecated this.textSeparator: ' ', + }) { if (text != null) { this._content.add(text); } - if (texts != null) { - this._content.addAll(texts); - } + this._content.addAll(texts); + this._children.addAll(children); } // TODO: A function that create an instance of ToDo (or Paragraph or Heading) from a Block. @@ -49,11 +57,30 @@ class ToDo extends Block { // } /// Add a new [text] to the paragraph content and returns this instance. + @Deprecated('Use `addText(Block)` instead') ToDo add(Text text) { this._content.add(text); return this; } + /// Add a [text] to the rich text array and returns this instance. Also can receive the [annotations] of the text. + ToDo addText(String text, {TextAnnotations? annotations}) { + this._content.add(Text(text, annotations: annotations)); + return this; + } + + /// Add a new [block] to the children and returns this instance. + ToDo addChild(Block block) { + this._children.add(block); + return this; + } + + /// Add a list of [blocks] to the children and returns this instance. + ToDo addChildren(List blocks) { + this._children.addAll(blocks); + return this; + } + /// Convert this to a valid json representation for the Notion API. @override Map toJson() => { @@ -63,6 +90,7 @@ class ToDo extends Block { 'text': _content .map((e) => e.toJson(textSeparator: textSeparator)) .toList(), + 'children': _children.map((e) => e.toJson()).toList(), 'checked': checked, } }; diff --git a/lib/notion/blocks/toggle.dart b/lib/notion/blocks/toggle.dart new file mode 100644 index 0000000..db15fd1 --- /dev/null +++ b/lib/notion/blocks/toggle.dart @@ -0,0 +1,63 @@ +import 'package:notion_api/notion/blocks/block.dart'; +import 'package:notion_api/notion/general/rich_text.dart'; +import 'package:notion_api/notion/general/types/notion_types.dart'; + +/// A representation of the Bulleted List Item Notion block object; +class Toggle extends Block { + /// The block type. Always Toggle. + @override + final BlockTypes type = BlockTypes.Toggle; + + List _content = []; + List _children = []; + + /// The content of this block. + List get content => _content.toList(); + + /// The children of this block. + List get children => _children.toList(); + + /// Main toggle constructor. + /// + /// Can receive a single [text] or a list of [texts]. If both are included also both fields are added to the heading content adding first the [text] field. Also can receive the [children] of the block. + Toggle({ + Text? text, + List texts: const [], + List children: const [], + }) { + if (text != null) { + _content.add(text); + } + _content.addAll(texts); + _children.addAll(children); + } + + /// Add a [text] to the rich text array and returns this instance. Also can receive the [annotations] of the text. + Toggle addText(String text, {TextAnnotations? annotations}) { + this._content.add(Text(text, annotations: annotations)); + return this; + } + + /// Add a new [block] to the children and returns this instance. + Toggle addChild(Block block) { + this._children.add(block); + return this; + } + + /// Add a list of [blocks] to the children and returns this instance. + Toggle addChildren(List blocks) { + this._children.addAll(blocks); + return this; + } + + /// Convert this to a valid json representation for the Notion API. + @override + Map toJson() => { + 'object': strObject, + 'type': strType, + strType: { + 'text': _content.map((e) => e.toJson()).toList(), + 'children': _children.map((e) => e.toJson()).toList(), + }, + }; +} diff --git a/lib/notion/general/lists/children.dart b/lib/notion/general/lists/children.dart index 4d0924c..d8417bb 100644 --- a/lib/notion/general/lists/children.dart +++ b/lib/notion/general/lists/children.dart @@ -19,11 +19,13 @@ class Children { /// Main children constructor. /// + /// _Parameters deprecated:_ Do not use the parameters, soon will be removed. + /// /// Can receive a single [heading], a single [paragraph], and a list of [toDo] blocks. If all three are included then the three fields are added to the blocks list adding first the [heading] field, then the [paragraph], and the list of [toDo] at the end. Children({ - Heading? heading, - Paragraph? paragraph, - List? toDo, + @deprecated Heading? heading, + @deprecated Paragraph? paragraph, + @deprecated List? toDo, }) { if (heading != null) { _blocks.add(heading); @@ -36,14 +38,20 @@ class Children { } } + /// Constructor that initialize a Children instance with a list of blocks. + /// + /// Receive a list of blocks and avoid create first the Children instance and then add the blocks. + Children.withBlocks(List blocks) { + this._blocks.addAll(blocks); + } + /// Map a new children instance from a [json] blocks list. /// /// The blocks with the block type None are excluded because that type represent blocks than can't be mapped as a knowing Notion block type. factory Children.fromJson(List json) { - Children children = Children(); - children._blocks = Block.fromListJson(json); - children._blocks = children.filterBlocks(exclude: [BlockTypes.None]); - return children; + List blocks = Block.fromListJson(json); + blocks.removeWhere((block) => block.type == BlockTypes.None); + return Children.withBlocks(blocks); } /// Add a new [block] to the list of blocks and returns this instance. diff --git a/lib/notion/general/rich_text.dart b/lib/notion/general/rich_text.dart index 58b7a78..47201b9 100644 --- a/lib/notion/general/rich_text.dart +++ b/lib/notion/general/rich_text.dart @@ -29,6 +29,8 @@ class Text { /// Convert this to a json representation valid for the Notion API. /// + ///_Deprecated:_ [textSeparator] will be removed and the separation will be by your own. This because that's the same way that `Text` & `RichText` works on Flutter. In this way you can add annotations for a part of a word instead of only full words or phrases. + /// /// If a [textSeparator] is given, then it's value (by default a space) is append /// at the end of the string to allow be at the same level of other Text objects without /// being all together. For example: @@ -55,7 +57,8 @@ class Text { /// textSeparator: '-'))); /// // append => "A-B-" /// ``` - Map toJson({String textSeparator: ''}) { + Map toJson( + {@Deprecated('Will not have replacement') String textSeparator: ''}) { Map json = { 'type': _type, 'text': { diff --git a/lib/notion/general/types/notion_types.dart b/lib/notion/general/types/notion_types.dart index 6eccc8a..f9d0e3c 100644 --- a/lib/notion/general/types/notion_types.dart +++ b/lib/notion/general/types/notion_types.dart @@ -5,8 +5,8 @@ enum BlockTypes { H2, H3, Paragraph, - BulletedList, - NumberedList, + BulletedListItem, + NumberedListItem, ToDo, Toggle, Child, diff --git a/lib/notion/objects/database.dart b/lib/notion/objects/database.dart index 0500ac6..9a8af1a 100644 --- a/lib/notion/objects/database.dart +++ b/lib/notion/objects/database.dart @@ -9,7 +9,7 @@ import 'package:notion_api/utils/utils.dart'; class Database extends BaseFields { /// The type of this object. Always Database for this. @override - ObjectTypes object = ObjectTypes.Database; + final ObjectTypes object = ObjectTypes.Database; /// The title of this database. List title = []; diff --git a/lib/notion/objects/pages.dart b/lib/notion/objects/pages.dart index 9761d56..ce57f27 100644 --- a/lib/notion/objects/pages.dart +++ b/lib/notion/objects/pages.dart @@ -10,7 +10,7 @@ import 'package:notion_api/utils/utils.dart'; class Page extends BaseFields { /// The type of this object. Always Page for this. @override - ObjectTypes object = ObjectTypes.Page; + final ObjectTypes object = ObjectTypes.Page; /// The information of the page parent. Parent parent; diff --git a/lib/notion_blocks.dart b/lib/notion_blocks.dart index b9922b7..4095dc5 100644 --- a/lib/notion_blocks.dart +++ b/lib/notion_blocks.dart @@ -1,26 +1,17 @@ import 'dart:convert'; import 'package:http/http.dart' as http; +import 'package:notion_api/base_client.dart'; import 'notion/general/lists/children.dart'; import 'responses/notion_response.dart'; import 'statics.dart'; /// A client for Notion API block children requests. -class NotionBlockClient { - /// The API integration secret token. - String _token; - - /// The API version. - String _v; - - /// The API date version. - /// - /// It's not the same as the API version. - String _dateVersion; - +class NotionBlockClient extends BaseClient { /// The path of the requests group. - String _path = 'blocks'; + @override + final String path = 'blocks'; /// Main Notion block client constructor. /// @@ -29,9 +20,7 @@ class NotionBlockClient { required String token, String version: latestVersion, String dateVersion: latestDateVersion, - }) : this._token = token, - this._v = version, - this._dateVersion = dateVersion; + }) : super(token: token, version: version, dateVersion: dateVersion); /// Retrieve the block children from block with [id]. /// @@ -51,9 +40,9 @@ class NotionBlockClient { } http.Response response = await http - .get(Uri.https(host, '/$_v/$_path/$id/children', query), headers: { - 'Authorization': 'Bearer $_token', - 'Notion-Version': _dateVersion, + .get(Uri.https(host, '/$v/$path/$id/children', query), headers: { + 'Authorization': 'Bearer $token', + 'Notion-Version': dateVersion, }); return NotionResponse.fromResponse(response); @@ -65,12 +54,12 @@ class NotionBlockClient { required Children children, }) async { http.Response res = await http.patch( - Uri.https(host, '/$_v/$_path/$to/children'), + Uri.https(host, '/$v/$path/$to/children'), body: jsonEncode(children.toJson()), headers: { - 'Authorization': 'Bearer $_token', + 'Authorization': 'Bearer $token', 'Content-Type': 'application/json; charset=UTF-8', - 'Notion-Version': _dateVersion, + 'Notion-Version': dateVersion, }); return NotionResponse.fromResponse(res); diff --git a/lib/notion_databases.dart b/lib/notion_databases.dart index 4578e7a..0a58c10 100644 --- a/lib/notion_databases.dart +++ b/lib/notion_databases.dart @@ -1,23 +1,14 @@ import 'package:http/http.dart' as http; +import 'package:notion_api/base_client.dart'; import 'responses/notion_response.dart'; import 'statics.dart'; /// A client for Notion API databases requests. -class NotionDatabasesClient { - /// The API integration secret token. - String _token; - - /// The API version. - String _v; - - /// The API date version. - /// - /// It's not the same as the API version. - String _dateVersion; - +class NotionDatabasesClient extends BaseClient { /// The path of the requests group. - String _path = 'databases'; + @override + final String path = 'databases'; /// Main Notion database client constructor. /// @@ -26,16 +17,14 @@ class NotionDatabasesClient { required String token, String version: latestVersion, String dateVersion: latestDateVersion, - }) : this._token = token, - this._v = version, - this._dateVersion = dateVersion; + }) : super(token: token, version: version, dateVersion: dateVersion); /// Retrieve the database with [id]. Future fetch(String id) async { http.Response res = - await http.get(Uri.https(host, '/$_v/$_path/$id'), headers: { - 'Authorization': 'Bearer $_token', - 'Notion-Version': _dateVersion, + await http.get(Uri.https(host, '/$v/$path/$id'), headers: { + 'Authorization': 'Bearer $token', + 'Notion-Version': dateVersion, }); return NotionResponse.fromResponse(res); @@ -55,9 +44,9 @@ class NotionDatabasesClient { } http.Response res = - await http.get(Uri.https(host, '/$_v/$_path', query), headers: { - 'Authorization': 'Bearer $_token', - 'Notion-Version': _dateVersion, + await http.get(Uri.https(host, '/$v/$path', query), headers: { + 'Authorization': 'Bearer $token', + 'Notion-Version': dateVersion, }); return NotionResponse.fromResponse(res); diff --git a/lib/notion_pages.dart b/lib/notion_pages.dart index 7f35ff1..830adaf 100644 --- a/lib/notion_pages.dart +++ b/lib/notion_pages.dart @@ -1,26 +1,17 @@ import 'dart:convert'; import 'package:http/http.dart' as http; +import 'package:notion_api/base_client.dart'; import 'notion/objects/pages.dart'; import 'responses/notion_response.dart'; import 'statics.dart'; /// A client for Notion API pages requests. -class NotionPagesClient { - /// The API integration secret token. - String _token; - - /// The API version. - String _v; - - /// The API date version. - /// - /// It's not the same as the API version. - String _dateVersion; - +class NotionPagesClient extends BaseClient { /// The path of the requests group. - String _path = 'pages'; + @override + final String path = 'pages'; /// Main Notion page client constructor. /// @@ -29,16 +20,14 @@ class NotionPagesClient { required String token, String version: latestVersion, String dateVersion: latestDateVersion, - }) : this._token = token, - this._v = version, - this._dateVersion = latestDateVersion; + }) : super(token: token, version: version, dateVersion: dateVersion); /// Retrieve the page with [id]. Future fetch(String id) async { http.Response res = - await http.get(Uri.https(host, '/$_v/$_path/$id'), headers: { - 'Authorization': 'Bearer $_token', - 'Notion-Version': _dateVersion, + await http.get(Uri.https(host, '/$v/$path/$id'), headers: { + 'Authorization': 'Bearer $token', + 'Notion-Version': dateVersion, }); return NotionResponse.fromResponse(res); @@ -46,12 +35,12 @@ class NotionPagesClient { /// Create a new [page]. Future create(Page page) async { - http.Response res = await http.post(Uri.https(host, '/$_v/$_path'), + http.Response res = await http.post(Uri.https(host, '/$v/$path'), body: jsonEncode(page.toJson()), headers: { - 'Authorization': 'Bearer $_token', + 'Authorization': 'Bearer $token', 'Content-Type': 'application/json; charset=UTF-8', - 'Notion-Version': _dateVersion, + 'Notion-Version': dateVersion, }); return NotionResponse.fromResponse(res); diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 5e39bdc..2d9760a 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -28,9 +28,9 @@ String blockTypeToString(BlockTypes type) { return 'heading_3'; case BlockTypes.Paragraph: return 'paragraph'; - case BlockTypes.BulletedList: + case BlockTypes.BulletedListItem: return 'bulleted_list_item'; - case BlockTypes.NumberedList: + case BlockTypes.NumberedListItem: return 'numbered_list_item'; case BlockTypes.Toggle: return 'toggle'; @@ -53,9 +53,9 @@ BlockTypes stringToBlockType(String type) { case 'paragraph': return BlockTypes.Paragraph; case 'bulleted_list_item': - return BlockTypes.BulletedList; + return BlockTypes.BulletedListItem; case 'numbered_list_item': - return BlockTypes.NumberedList; + return BlockTypes.NumberedListItem; case 'toogle': return BlockTypes.Toggle; case 'to_do': diff --git a/pubspec.yaml b/pubspec.yaml index 3d15cef..88510a4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: notion_api description: A wrapper for the public beta Notion API to manage it like a Notion SDK package for dart. -version: 1.0.2 +version: 1.1.0 homepage: https://github.com/jonathangomz/notion_api environment: diff --git a/test/blocks/bulleted_list_item_test.dart b/test/blocks/bulleted_list_item_test.dart new file mode 100644 index 0000000..4267b32 --- /dev/null +++ b/test/blocks/bulleted_list_item_test.dart @@ -0,0 +1,86 @@ +import 'package:notion_api/notion/blocks/bulleted_list_item.dart'; +import 'package:notion_api/notion/blocks/paragraph.dart'; +import 'package:notion_api/notion/general/rich_text.dart'; +import 'package:notion_api/notion/general/types/notion_types.dart'; +import 'package:notion_api/utils/utils.dart'; +import 'package:test/test.dart'; + +void main() { + group('BulletedListItem tests =>', () { + test('Create an empty instance', () { + BulletedListItem block = BulletedListItem(); + + expect(block, isNotNull); + expect(block.strType, blockTypeToString(BlockTypes.BulletedListItem)); + expect(block.content, allOf([isList, isEmpty])); + expect(block.children, allOf([isList, isEmpty])); + expect(block.isBulletedItem, true); + expect(block.type, BlockTypes.BulletedListItem); + }); + + test('Create an instance with information', () { + BulletedListItem block = BulletedListItem(text: Text('A')).addText('B'); + + expect(block.content.length, 2); + expect(block.content.first.text, 'A'); + expect(block.content.last.text, 'B'); + }); + + test('Create an instance with mixed information', () { + BulletedListItem block = BulletedListItem( + text: Text('first'), + texts: [ + Text('foo'), + Text('bar'), + ], + ).addText('last').addChild(Paragraph(texts: [ + Text('A'), + Text('B'), + ])); + + expect(block.content.length, 4); + expect(block.content.first.text, 'first'); + expect(block.content.last.text, 'last'); + expect(block.children.length, 1); + }); + + test('Create json from instance', () { + Map json = BulletedListItem(text: Text('A')) + .addChild(Paragraph(texts: [ + Text('A'), + Text('B'), + ])) + .toJson(); + + expect( + json['type'], + allOf([ + isNotNull, + isNotEmpty, + blockTypeToString(BlockTypes.BulletedListItem) + ])); + expect(json, contains(blockTypeToString(BlockTypes.BulletedListItem))); + expect(json[blockTypeToString(BlockTypes.BulletedListItem)]['text'], + allOf([isList, isNotEmpty])); + expect(json[blockTypeToString(BlockTypes.BulletedListItem)]['children'], + allOf([isList, isNotEmpty])); + }); + + test('Create json from empty instance', () { + Map json = BulletedListItem().toJson(); + + expect( + json['type'], + allOf([ + isNotNull, + isNotEmpty, + blockTypeToString(BlockTypes.BulletedListItem) + ])); + expect(json, contains(blockTypeToString(BlockTypes.BulletedListItem))); + expect(json[blockTypeToString(BlockTypes.BulletedListItem)]['text'], + allOf([isList, isEmpty])); + expect(json[blockTypeToString(BlockTypes.BulletedListItem)]['children'], + allOf([isList, isEmpty])); + }); + }); +} diff --git a/test/blocks/heading_test.dart b/test/blocks/heading_test.dart index 375cdb0..5955ce0 100644 --- a/test/blocks/heading_test.dart +++ b/test/blocks/heading_test.dart @@ -33,7 +33,7 @@ void main() { }); test('Create an instance with information', () { - Heading heading = Heading(text: Text('A')).add(Text('B')); + Heading heading = Heading(text: Text('A')).addText('B'); expect(heading.content.length, 2); expect(heading.content.first.text, 'A'); @@ -43,7 +43,7 @@ void main() { test('Create an instance with mixed information', () { Heading heading = Heading(text: Text('first'), texts: [Text('foo'), Text('bar')]) - .add(Text('last')); + .addText('last'); expect(heading.content.length, 4); expect(heading.content.first.text, 'first'); diff --git a/test/blocks/numbered_list_item_test.dart b/test/blocks/numbered_list_item_test.dart new file mode 100644 index 0000000..6a567b2 --- /dev/null +++ b/test/blocks/numbered_list_item_test.dart @@ -0,0 +1,85 @@ +import 'package:notion_api/notion/blocks/numbered_list_item.dart'; +import 'package:notion_api/notion/blocks/paragraph.dart'; +import 'package:notion_api/notion/general/rich_text.dart'; +import 'package:notion_api/notion/general/types/notion_types.dart'; +import 'package:notion_api/utils/utils.dart'; +import 'package:test/test.dart'; + +void main() { + group('NumberedListItem tests =>', () { + test('Create an empty instance', () { + NumberedListItem block = NumberedListItem(); + + expect(block, isNotNull); + expect(block.strType, blockTypeToString(BlockTypes.NumberedListItem)); + expect(block.content, allOf([isList, isEmpty])); + expect(block.isNumberedItem, true); + expect(block.type, BlockTypes.NumberedListItem); + }); + + test('Create an instance with information', () { + NumberedListItem block = NumberedListItem(text: Text('A')).addText('B'); + + expect(block.content.length, 2); + expect(block.content.first.text, 'A'); + expect(block.content.last.text, 'B'); + }); + + test('Create an instance with mixed information', () { + NumberedListItem block = NumberedListItem( + text: Text('first'), + texts: [ + Text('foo'), + Text('bar'), + ], + ).addText('last').addChild(Paragraph(texts: [ + Text('A'), + Text('B'), + ])); + + expect(block.content.length, 4); + expect(block.content.first.text, 'first'); + expect(block.content.last.text, 'last'); + expect(block.children.length, 1); + }); + + test('Create json from instance', () { + Map json = NumberedListItem(text: Text('A')) + .addChild(Paragraph(texts: [ + Text('A'), + Text('B'), + ])) + .toJson(); + + expect( + json['type'], + allOf([ + isNotNull, + isNotEmpty, + blockTypeToString(BlockTypes.NumberedListItem) + ])); + expect(json, contains(blockTypeToString(BlockTypes.NumberedListItem))); + expect(json[blockTypeToString(BlockTypes.NumberedListItem)]['text'], + allOf([isList, isNotEmpty])); + expect(json[blockTypeToString(BlockTypes.NumberedListItem)]['children'], + allOf([isList, isNotEmpty])); + }); + + test('Create json from empty instance', () { + Map json = NumberedListItem().toJson(); + + expect( + json['type'], + allOf([ + isNotNull, + isNotEmpty, + blockTypeToString(BlockTypes.NumberedListItem) + ])); + expect(json, contains(blockTypeToString(BlockTypes.NumberedListItem))); + expect(json[blockTypeToString(BlockTypes.NumberedListItem)]['text'], + allOf([isList, isEmpty])); + expect(json[blockTypeToString(BlockTypes.NumberedListItem)]['children'], + allOf([isList, isEmpty])); + }); + }); +} diff --git a/test/blocks/paragraph_test.dart b/test/blocks/paragraph_test.dart index 7faf7ee..629952a 100644 --- a/test/blocks/paragraph_test.dart +++ b/test/blocks/paragraph_test.dart @@ -11,32 +11,43 @@ void main() { expect(paragraph, isNotNull); expect(paragraph.strType, blockTypeToString(BlockTypes.Paragraph)); - expect(paragraph.texts, allOf([isList, isEmpty])); + expect(paragraph.content, allOf([isList, isEmpty])); expect(paragraph.isParagraph, true); expect(paragraph.type, BlockTypes.Paragraph); }); test('Create an instance with information', () { - Paragraph paragraph = Paragraph().add(Text('A')).add(Text('B')); + Paragraph paragraph = Paragraph().addText('A').addText('B'); - expect(paragraph.texts.length, 2); - expect(paragraph.texts.first.text, 'A'); - expect(paragraph.texts.last.text, 'B'); + expect(paragraph.content.length, 2); + expect(paragraph.content.first.text, 'A'); + expect(paragraph.content.last.text, 'B'); }); test('Create an instance with mixed information', () { Paragraph paragraph = Paragraph(text: Text('first'), texts: [Text('foo'), Text('bar')]) - .add(Text('last')); + .addText('last') + .addChild(Paragraph(texts: [ + Text('A'), + Text('B'), + ])); - expect(paragraph.texts.length, 4); - expect(paragraph.texts.first.text, 'first'); - expect(paragraph.texts.last.text, 'last'); + expect(paragraph.content.length, 4); + expect(paragraph.content.first.text, 'first'); + expect(paragraph.content.last.text, 'last'); + expect(paragraph.children.length, 1); }); test('Create json from instance', () { - Map json = - Paragraph().add(Text('A')).add(Text('B')).toJson(); + Map json = Paragraph() + .addText('A') + .addText('B') + .addChild(Paragraph(texts: [ + Text('A'), + Text('B'), + ])) + .toJson(); expect( json['type'], @@ -48,6 +59,8 @@ void main() { expect(json, contains(blockTypeToString(BlockTypes.Paragraph))); expect(json[blockTypeToString(BlockTypes.Paragraph)]['text'], allOf([isList, isNotEmpty])); + expect(json[blockTypeToString(BlockTypes.Paragraph)]['children'], + allOf([isList, isNotEmpty])); }); test('Create json from emppty instance', () { @@ -63,6 +76,8 @@ void main() { expect(json, contains(blockTypeToString(BlockTypes.Paragraph))); expect(json[blockTypeToString(BlockTypes.Paragraph)]['text'], allOf([isList, isEmpty])); + expect(json[blockTypeToString(BlockTypes.Paragraph)]['children'], + allOf([isList, isEmpty])); }); test('Create json with separator', () { @@ -70,7 +85,7 @@ void main() { String separator = '-'; Map json = - Paragraph(textSeparator: separator).add(Text(char)).toJson(); + Paragraph(textSeparator: separator).addText(char).toJson(); List jsonTexts = json[blockTypeToString(BlockTypes.Paragraph)]['text']; diff --git a/test/blocks/todo_test.dart b/test/blocks/todo_test.dart index 86ebe66..ddabfbe 100644 --- a/test/blocks/todo_test.dart +++ b/test/blocks/todo_test.dart @@ -1,3 +1,4 @@ +import 'package:notion_api/notion/blocks/paragraph.dart'; import 'package:notion_api/notion/blocks/todo.dart'; import 'package:notion_api/notion/general/rich_text.dart'; import 'package:notion_api/notion/general/types/notion_types.dart'; @@ -18,7 +19,7 @@ void main() { }); test('Create an instance with information', () { - ToDo todo = ToDo(text: Text('A'), checked: true).add(Text('B')); + ToDo todo = ToDo(text: Text('A'), checked: true).addText('B'); expect(todo.checked, true); expect(todo.content.length, 2); @@ -34,22 +35,33 @@ void main() { Text('bar'), ], checked: true, - ).add(Text('last')); + ).addText('last').addChild(Paragraph(texts: [ + Text('A'), + Text('B'), + ])); expect(todo.checked, true); expect(todo.content.length, 4); expect(todo.content.first.text, 'first'); expect(todo.content.last.text, 'last'); + expect(todo.children.length, 1); }); test('Create json from instance', () { - Map json = ToDo(text: Text('A')).toJson(); + Map json = ToDo(text: Text('A')) + .addChild(Paragraph(texts: [ + Text('A'), + Text('B'), + ])) + .toJson(); expect(json['type'], allOf([isNotNull, isNotEmpty, blockTypeToString(BlockTypes.ToDo)])); expect(json, contains(blockTypeToString(BlockTypes.ToDo))); expect(json[blockTypeToString(BlockTypes.ToDo)]['text'], allOf([isList, isNotEmpty])); + expect(json[blockTypeToString(BlockTypes.ToDo)]['children'], + allOf([isList, isNotEmpty])); }); test('Create json from empty instance', () { @@ -60,6 +72,8 @@ void main() { expect(json, contains(blockTypeToString(BlockTypes.ToDo))); expect(json[blockTypeToString(BlockTypes.ToDo)]['text'], allOf([isList, isEmpty])); + expect(json[blockTypeToString(BlockTypes.ToDo)]['children'], + allOf([isList, isEmpty])); }); }); } diff --git a/test/blocks/toggle_test.dart b/test/blocks/toggle_test.dart new file mode 100644 index 0000000..9cb07a4 --- /dev/null +++ b/test/blocks/toggle_test.dart @@ -0,0 +1,88 @@ +import 'package:notion_api/notion/blocks/bulleted_list_item.dart'; +import 'package:notion_api/notion/blocks/numbered_list_item.dart'; +import 'package:notion_api/notion/blocks/paragraph.dart'; +import 'package:notion_api/notion/blocks/toggle.dart'; +import 'package:notion_api/notion/general/rich_text.dart'; +import 'package:notion_api/notion/general/types/notion_types.dart'; +import 'package:notion_api/utils/utils.dart'; +import 'package:test/test.dart'; + +void main() { + group('Toggle tests =>', () { + test('Create an empty instance', () { + Toggle block = Toggle(); + + expect(block, isNotNull); + expect(block.strType, blockTypeToString(BlockTypes.Toggle)); + expect(block.content, allOf([isList, isEmpty])); + expect(block.children, allOf([isList, isEmpty])); + expect(block.isToogle, true); + expect(block.type, BlockTypes.Toggle); + }); + + test('Create an instance with information', () { + Toggle block = Toggle(text: Text('A')) + .addText('B') + .addChild( + Paragraph(text: Text('This is a child of the toggle item.'))) + .addChildren([ + BulletedListItem(text: Text('First bulleted item')), + NumberedListItem(text: Text('First numbered item')), + ]); + + expect(block.content.length, 2); + expect(block.content.first.text, 'A'); + expect(block.content.last.text, 'B'); + expect(block.children.length, 3); + }); + + test('Create an instance with mixed information', () { + Toggle block = Toggle( + text: Text('first'), + texts: [ + Text('foo'), + Text('bar'), + ], + ) + .addText('last') + .addChild( + Paragraph(text: Text('This is a child of the toggle item.'))) + .addChildren([ + BulletedListItem(text: Text('First bulleted item')), + NumberedListItem(text: Text('First numbered item')), + ]); + + expect(block.content.length, 4); + expect(block.content.first.text, 'first'); + expect(block.content.last.text, 'last'); + expect(block.children.length, 3); + }); + + test('Create json from instance', () { + Map json = Toggle(text: Text('A'), children: [ + BulletedListItem(text: Text('First bulleted item')), + NumberedListItem(text: Text('First numbered item')), + ]).toJson(); + + expect(json['type'], + allOf([isNotNull, isNotEmpty, blockTypeToString(BlockTypes.Toggle)])); + expect(json, contains(blockTypeToString(BlockTypes.Toggle))); + expect(json[blockTypeToString(BlockTypes.Toggle)]['text'], + allOf([isList, isNotEmpty])); + expect(json[blockTypeToString(BlockTypes.Toggle)]['children'], + allOf([isList, isNotEmpty])); + }); + + test('Create json from empty instance', () { + Map json = Toggle().toJson(); + + expect(json['type'], + allOf([isNotNull, isNotEmpty, blockTypeToString(BlockTypes.Toggle)])); + expect(json, contains(blockTypeToString(BlockTypes.Toggle))); + expect(json[blockTypeToString(BlockTypes.Toggle)]['text'], + allOf([isList, isEmpty])); + expect(json[blockTypeToString(BlockTypes.Toggle)]['children'], + allOf([isList, isEmpty])); + }); + }); +} diff --git a/test/lists/children_test.dart b/test/lists/children_test.dart index 5866531..a1f7b79 100644 --- a/test/lists/children_test.dart +++ b/test/lists/children_test.dart @@ -86,7 +86,7 @@ void main() { }); test('Add blocks in distinct ways', () { - Children children1 = Children( + Children deprecated = Children( heading: Heading(text: Text('Test')), paragraph: Paragraph( texts: [ @@ -103,6 +103,23 @@ void main() { ), ); + Children children1 = Children.withBlocks([ + Heading(text: Text('Test')), + Paragraph( + texts: [ + Text('Lorem ipsum (A)'), + Text( + 'Lorem ipsum (B)', + annotations: TextAnnotations( + bold: true, + underline: true, + color: ColorsTypes.Orange, + ), + ), + ], + ), + ]); + Children children2 = Children().add(Heading(text: Text('Test'))).add(Paragraph(texts: [ Text('Lorem ipsum (A)'), @@ -127,10 +144,12 @@ void main() { ]) ]); + var json0 = deprecated.toJson(); var json1 = children1.toJson(); var json2 = children2.toJson(); var json3 = children3.toJson(); + expect(json0, json1); expect(json1, json2); expect(json2, json3); }); diff --git a/test/notion_api_test.dart b/test/notion_api_test.dart index c795686..2e8dc17 100644 --- a/test/notion_api_test.dart +++ b/test/notion_api_test.dart @@ -1,9 +1,13 @@ +import 'dart:convert'; import 'dart:io' show Platform; import 'package:dotenv/dotenv.dart' show load, env, clean; +import 'package:notion_api/notion/blocks/bulleted_list_item.dart'; import 'package:notion_api/notion/blocks/heading.dart'; +import 'package:notion_api/notion/blocks/numbered_list_item.dart'; import 'package:notion_api/notion/blocks/paragraph.dart'; import 'package:notion_api/notion/blocks/todo.dart'; +import 'package:notion_api/notion/blocks/toggle.dart'; import 'package:notion_api/notion/general/types/notion_types.dart'; import 'package:notion_api/notion/general/lists/children.dart'; import 'package:notion_api/notion/objects/pages.dart'; @@ -159,21 +163,22 @@ void main() { NotionResponse res = await blocks.append( to: testBlockId as String, - children: Children().add(Heading(text: Text('Test'))).add( - Paragraph( - texts: [ - Text('Lorem ipsum (A)'), - Text( - 'Lorem ipsum (B)', - annotations: TextAnnotations( - bold: true, - underline: true, - color: ColorsTypes.Orange, - ), - ), - ], + children: Children.withBlocks([ + Heading(text: Text('Test')), + Paragraph(texts: [ + Text('Lorem ipsum (A)'), + Text( + 'Lorem ipsum (B)', + annotations: TextAnnotations( + bold: true, + underline: true, + color: ColorsTypes.Orange, ), ), + ], children: [ + Heading(text: Text('Subtitle'), type: 3), + ]), + ]), ); expect(res.status, 200); @@ -185,16 +190,101 @@ void main() { NotionResponse res = await blocks.append( to: testBlockId as String, - children: Children().addAll( + children: Children.withBlocks([ + ToDo(text: Text('This is a todo item A')), + ToDo( + texts: [ + Text('This is a todo item'), + Text( + 'B', + annotations: TextAnnotations(bold: true), + ), + ], + ), + ToDo(text: Text('Todo item with children'), children: [ + BulletedListItem(text: Text('A')), + BulletedListItem(text: Text('B')), + ]) + ]), + ); + + expect(res.status, 200); + expect(res.isOk, true); + }); + + test('Append bulleted list item block', () async { + final NotionBlockClient blocks = NotionBlockClient(token: token ?? ''); + + NotionResponse res = await blocks.append( + to: testBlockId as String, + children: Children.withBlocks( + [ + BulletedListItem(text: Text('This is a bulleted list item A')), + BulletedListItem(text: Text('This is a bulleted list item B')), + BulletedListItem( + text: Text('This is a bulleted list item with children'), + children: [ + Paragraph(texts: [ + Text('A'), + Text('B'), + Text('C'), + ]) + ], + ), + ], + ), + ); + + expect(res.status, 200); + expect(res.isOk, true); + }); + + test('Append numbered list item block', () async { + final NotionBlockClient blocks = NotionBlockClient(token: token ?? ''); + + NotionResponse res = await blocks.append( + to: testBlockId as String, + children: Children.withBlocks( + [ + NumberedListItem(text: Text('This is a numbered list item A')), + NumberedListItem(text: Text('This is a numbered list item B')), + NumberedListItem( + text: Text('This is a bulleted list item with children'), + children: [ + Paragraph(texts: [ + Text('A'), + Text('B'), + Text('C'), + ]) + ], + ), + ], + ), + ); + + expect(res.status, 200); + expect(res.isOk, true); + }); + + test('Append toggle block', () async { + final NotionBlockClient blocks = NotionBlockClient(token: token ?? ''); + + NotionResponse res = await blocks.append( + to: testBlockId as String, + children: Children.withBlocks( [ - ToDo(text: Text('This is a todo item A')), - ToDo( - texts: [ - Text('This is a todo item'), - Text( - 'B', - annotations: TextAnnotations(bold: true), + Toggle( + text: Text('This is a toggle block'), + children: [ + Paragraph( + texts: [ + Text( + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas venenatis dolor sed ex egestas, et vehicula tellus faucibus. Sed pellentesque tellus eget imperdiet vulputate.') + ], ), + BulletedListItem(text: Text('A')), + BulletedListItem(text: Text('B')), + BulletedListItem(text: Text('B')), ], ), ], diff --git a/test/objects/page_test.dart b/test/objects/page_test.dart index ca444d6..152f62f 100644 --- a/test/objects/page_test.dart +++ b/test/objects/page_test.dart @@ -147,7 +147,9 @@ void main() { Page pageWithChildren = Page( parent: parent, - children: Children(heading: Heading(text: Text('A')))); + children: Children.withBlocks([ + Heading(text: Text('A')), + ])); Page pageWithoutChildren = Page(parent: parent); Map jsonWithChildren = pageWithChildren.toJson(); diff --git a/test/response_test.dart b/test/response_test.dart index c6731c0..312fdf2 100644 --- a/test/response_test.dart +++ b/test/response_test.dart @@ -2,6 +2,8 @@ import 'dart:io' show Platform; import 'package:dotenv/dotenv.dart' show load, env, clean; import 'package:notion_api/notion.dart'; +import 'package:notion_api/notion/blocks/paragraph.dart'; +import 'package:notion_api/notion/general/lists/children.dart'; import 'package:notion_api/notion/general/property.dart'; import 'package:notion_api/notion/general/rich_text.dart'; import 'package:notion_api/notion/general/types/notion_types.dart'; @@ -17,6 +19,7 @@ void main() { String? testDatabaseId = Platform.environment['TEST_DATABASE_ID']; String? testPageId = Platform.environment['TEST_PAGE_ID']; String? testBlockId = Platform.environment['TEST_BLOCK_ID']; + String? testBlockHeadingId = Platform.environment['TEST_BLOCK_HEADING_ID']; String execEnv = env['EXEC_ENV'] ?? Platform.environment['EXEC_ENV'] ?? ''; if (execEnv != 'github_actions') { @@ -27,6 +30,8 @@ void main() { testDatabaseId = env['TEST_DATABASE_ID'] ?? testDatabaseId ?? ''; testPageId = env['TEST_PAGE_ID'] ?? testPageId ?? ''; testBlockId = env['TEST_BLOCK_ID'] ?? testBlockId ?? ''; + testBlockHeadingId = + env['TEST_BLOCK_HEADING_ID'] ?? testBlockHeadingId ?? ''; }); tearDownAll(() { @@ -55,6 +60,29 @@ void main() { expect(res.code, 'unauthorized'); }); + test('Invalid field (children) for block', () async { + final NotionBlockClient blocks = NotionBlockClient(token: token ?? ''); + + // Heading block do not support children + var res = await blocks.append( + to: testBlockHeadingId ?? '', + children: Children.withBlocks( + [ + Paragraph( + texts: [ + Text('A'), + Text('B'), + ], + ) + ], + ), + ); + + expect(res.status, 400); + expect(res.isError, true); + expect(res.code, 'validation_error'); + }); + test('Invalid property', () async { final NotionPagesClient pages = NotionPagesClient(token: token ?? ''); diff --git a/test/utils_test.dart b/test/utils_test.dart index 4deffc8..c227db2 100644 --- a/test/utils_test.dart +++ b/test/utils_test.dart @@ -151,8 +151,8 @@ void main() { group('(Types to String) || (String to Type tests) =>', () { test('Block types', () { String strParagraph = blockTypeToString(BlockTypes.Paragraph); - String strBulleted = blockTypeToString(BlockTypes.BulletedList); - String strNumbered = blockTypeToString(BlockTypes.NumberedList); + String strBulleted = blockTypeToString(BlockTypes.BulletedListItem); + String strNumbered = blockTypeToString(BlockTypes.NumberedListItem); String strToogle = blockTypeToString(BlockTypes.Toggle); String strChild = blockTypeToString(BlockTypes.Child);