Compare commits

..

23 Commits

Author SHA1 Message Date
Peter Zignego 267cf26b2e Merge pull request #165 from vasilenkoigor/UsersLookupByEmail-Endpoint
Added missed users.lookupByEmail endpoint
2019-07-25 21:20:42 -04:00
i.v.vasilenko c3817bea15 Added missed parameter for users.lookupByEmail endpoint 2019-07-21 21:53:30 +03:00
i.v.vasilenko 492f51ce9b Added missed endpoint users.lookupByEmail 2019-07-21 21:33:38 +03:00
Peter Zignego dd72c619d3 Merge pull request #164 from RobotsAndPencils/add_block_support
Adding Blocks support
2019-07-17 09:50:25 -04:00
Peter Zignego 43af10de88 Merge branch 'master' into add_block_support 2019-07-17 09:43:55 -04:00
Brad Brown 7ba97e4893 Adding Blocks support 2019-07-16 09:26:14 -05:00
Peter Zignego 0c24cb2262 Merge pull request #163 from RomanPodymov/master
Action, AttachmentField, Edited and Reply conform to Codable
2019-07-12 10:30:23 -04:00
Roman Podymov 7b76b76b94 Update XCTestManifests.swift 2019-06-26 00:33:52 +02:00
Roman Podymov 565c44677b Update XCTestManifests.swift 2019-06-26 00:33:38 +02:00
Roman Podymov 30dc3679c1 Fixed tests 2019-06-26 00:26:19 +02:00
Roman Podymov 9a18cae265 removed \n 2019-06-23 22:39:44 +02:00
Roman Podymov c04654a87b tests for Action 2019-06-23 22:38:45 +02:00
Roman Podymov bd7f67b3b0 attachmentfield tests 2019-06-22 17:03:01 +02:00
Roman Podymov 833add707f removed \n 2019-06-22 16:49:28 +02:00
Roman Podymov 9118a7688a tests for Reply (added missing files) 2019-06-22 16:46:36 +02:00
Roman Podymov cba1eb36a2 Tests for Reply 2019-06-22 16:45:11 +02:00
Roman Podymov d15139d00d Update Reply.swift 2019-06-22 14:53:35 +02:00
Roman Podymov c1cf9b47d8 Update AttachmentField.swift 2019-06-22 14:49:43 +02:00
Roman Podymov 986367be38 Update Action.swift 2019-06-22 14:48:08 +02:00
Peter Zignego 7d207136b3 Merge pull request #162 from pvzig/ci-update
Update to Azure Pipelines to macOS 10.14 and Xcode 10.2
2019-04-11 20:40:16 -04:00
Peter Zignego 0fa3b72a56 Workspace path 2019-04-11 20:36:18 -04:00
Peter Zignego 67d2bb3f62 xcodeVersion 2019-04-11 20:08:26 -04:00
Peter Zignego f2c333f57d Update to macOS 10.14 and Xcode 10.2 2019-04-11 20:01:40 -04:00
17 changed files with 1016 additions and 65 deletions
+126 -36
View File
@@ -22,6 +22,18 @@
// THE SOFTWARE.
public struct Action {
fileprivate enum CodingKeys: String {
case name
case text
case type
case value
case url
case style
case confirm
case options
case dataSource = "data_source"
}
public let name: String?
public let text: String?
public let type: String?
@@ -31,17 +43,17 @@ public struct Action {
public let confirm: Confirm?
public let options: [Option]?
public let dataSource: DataSource?
public init(action: [String: Any]?) {
name = action?["name"] as? String
text = action?["text"] as? String
type = action?["type"] as? String
value = action?["value"] as? String
url = action?["url"] as? String
style = ActionStyle(rawValue: action?["style"] as? String ?? "")
confirm = Confirm(confirm:action?["confirm"] as? [String: Any])
options = (action?["options"] as? [[String: Any]])?.map { Option(option: $0) }
dataSource = DataSource(rawValue: action?["data_source"] as? String ?? "")
name = action?[CodingKeys.name.rawValue] as? String
text = action?[CodingKeys.text.rawValue] as? String
type = action?[CodingKeys.type.rawValue] as? String
value = action?[CodingKeys.value.rawValue] as? String
url = action?[CodingKeys.url.rawValue] as? String
style = ActionStyle(rawValue: action?[CodingKeys.style.rawValue] as? String ?? "")
confirm = Confirm(confirm:action?[CodingKeys.confirm.rawValue] as? [String: Any])
options = (action?[CodingKeys.options.rawValue] as? [[String: Any]])?.map { Option(option: $0) }
dataSource = DataSource(rawValue: action?[CodingKeys.dataSource.rawValue] as? String ?? "")
}
public init(name: String, text: String, type: String = "button", style: ActionStyle = .defaultStyle, value: String? = nil,
@@ -59,29 +71,36 @@ public struct Action {
public var dictionary: [String: Any] {
var dict = [String: Any]()
dict["name"] = name
dict["text"] = text
dict["type"] = type
dict["value"] = value
dict["url"] = url
dict["style"] = style?.rawValue
dict["confirm"] = confirm?.dictionary
dict["options"] = options?.map { $0.dictionary }
dict["data_source"] = dataSource?.rawValue
dict[CodingKeys.name.rawValue] = name
dict[CodingKeys.text.rawValue] = text
dict[CodingKeys.type.rawValue] = type
dict[CodingKeys.value.rawValue] = value
dict[CodingKeys.url.rawValue] = url
dict[CodingKeys.style.rawValue] = style?.rawValue
dict[CodingKeys.confirm.rawValue] = confirm?.dictionary
dict[CodingKeys.options.rawValue] = options?.map { $0.dictionary }
dict[CodingKeys.dataSource.rawValue] = dataSource?.rawValue
return dict
}
public struct Confirm {
fileprivate enum CodingKeys: String {
case title
case text
case okText = "ok_text"
case dismissText = "dismiss_text"
}
public let title: String?
public let text: String?
public let okText: String?
public let dismissText: String?
public init(confirm: [String: Any]?) {
title = confirm?["title"] as? String
text = confirm?["text"] as? String
okText = confirm?["ok_text"] as? String
dismissText = confirm?["dismiss_text"] as? String
title = confirm?[CodingKeys.title.rawValue] as? String
text = confirm?[CodingKeys.text.rawValue] as? String
okText = confirm?[CodingKeys.okText.rawValue] as? String
dismissText = confirm?[CodingKeys.dismissText.rawValue] as? String
}
public init(text: String, title: String? = nil, okText: String? = nil, dismissText: String? = nil) {
@@ -93,21 +112,26 @@ public struct Action {
public var dictionary: [String: Any] {
var dict = [String: Any]()
dict["title"] = title
dict["text"] = text
dict["ok_text"] = okText
dict["dismiss_text"] = dismissText
dict[CodingKeys.title.rawValue] = title
dict[CodingKeys.text.rawValue] = text
dict[CodingKeys.okText.rawValue] = okText
dict[CodingKeys.dismissText.rawValue] = dismissText
return dict
}
}
public struct Option {
fileprivate enum CodingKeys: String {
case text
case value
}
public let text: String?
public let value: String?
public init(option: [String: Any]?) {
text = option?["text"] as? String
value = option?["value"] as? String
text = option?[CodingKeys.text.rawValue] as? String
value = option?[CodingKeys.value.rawValue] as? String
}
public init(text: String, value: String) {
@@ -117,26 +141,92 @@ public struct Action {
public var dictionary: [String: Any] {
var dict = [String: Any]()
dict["text"] = text
dict["value"] = value
dict[CodingKeys.text.rawValue] = text
dict[CodingKeys.value.rawValue] = value
return dict
}
}
public enum DataSource: String {
public enum DataSource: String, Codable {
case users
case channels
case conversations
}
}
public enum ActionStyle: String {
extension Action: Codable {
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
name = try values.decodeIfPresent(String.self, forKey: .name)
text = try values.decodeIfPresent(String.self, forKey: .text)
type = try values.decodeIfPresent(String.self, forKey: .type)
value = try values.decodeIfPresent(String.self, forKey: .value)
url = try values.decodeIfPresent(String.self, forKey: .url)
style = try values.decodeIfPresent(ActionStyle.self, forKey: .style)
confirm = try values.decodeIfPresent(Confirm.self, forKey: .confirm)
options = try values.decodeIfPresent([Option].self, forKey: .options)
dataSource = try values.decodeIfPresent(DataSource.self, forKey: .dataSource)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
try container.encode(text, forKey: .text)
try container.encode(type, forKey: .type)
try container.encode(value, forKey: .value)
try container.encode(url, forKey: .url)
try container.encode(style, forKey: .style)
try container.encode(confirm, forKey: .confirm)
try container.encode(options, forKey: .options)
try container.encode(dataSource, forKey: .dataSource)
}
}
extension Action.CodingKeys: CodingKey { }
extension Action.Confirm: Codable {
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
title = try values.decodeIfPresent(String.self, forKey: .title)
text = try values.decodeIfPresent(String.self, forKey: .text)
okText = try values.decodeIfPresent(String.self, forKey: .okText)
dismissText = try values.decodeIfPresent(String.self, forKey: .dismissText)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(title, forKey: .title)
try container.encode(text, forKey: .text)
try container.encode(okText, forKey: .okText)
try container.encode(dismissText, forKey: .dismissText)
}
}
extension Action.Confirm.CodingKeys: CodingKey { }
extension Action.Option: Codable {
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
text = try values.decodeIfPresent(String.self, forKey: .text)
value = try values.decodeIfPresent(String.self, forKey: .value)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(text, forKey: .text)
try container.encode(value, forKey: .value)
}
}
extension Action.Option.CodingKeys: CodingKey { }
public enum ActionStyle: String, Codable {
case defaultStyle = "default"
case primary = "primary"
case danger = "danger"
}
public enum MessageResponseType: String {
public enum MessageResponseType: String, Codable {
case inChannel = "in_channel"
case ephemeral = "ephemeral"
}
+31 -7
View File
@@ -22,14 +22,20 @@
// THE SOFTWARE.
public struct AttachmentField {
fileprivate enum CodingKeys: String {
case title
case value
case short
}
public let title: String?
public let value: String?
public let short: Bool?
public init(field: [String: Any]?) {
title = field?["title"] as? String
value = field?["value"] as? String
short = field?["short"] as? Bool
title = field?[CodingKeys.title.rawValue] as? String
value = field?[CodingKeys.value.rawValue] as? String
short = field?[CodingKeys.short.rawValue] as? Bool
}
public init(title: String?, value: String?, short: Bool? = nil) {
@@ -40,9 +46,27 @@ public struct AttachmentField {
public var dictionary: [String: Any] {
var field = [String: Any]()
field["title"] = title
field["value"] = value
field["short"] = short
field[CodingKeys.title.rawValue] = title
field[CodingKeys.value.rawValue] = value
field[CodingKeys.short.rawValue] = short
return field
}
}
extension AttachmentField: Codable {
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
title = try values.decodeIfPresent(String.self, forKey: .title)
value = try values.decodeIfPresent(String.self, forKey: .value)
short = try values.decodeIfPresent(Bool.self, forKey: .short)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(title, forKey: .title)
try container.encode(value, forKey: .value)
try container.encode(short, forKey: .short)
}
}
extension AttachmentField.CodingKeys: CodingKey { }
+96
View File
@@ -0,0 +1,96 @@
/// Defined by https://api.slack.com/reference/messaging/composition-objects#text
public struct TextComposition {
/// The type of block. Can be one of plainText or markdown.
public let type: BlockType
public let text: String
public let emoji: Bool?
public let verbatim: Bool?
public init(type: BlockType,
text: String,
emoji: Bool? = nil,
verbatim: Bool? = nil) {
self.type = type
self.text = text
self.emoji = emoji
self.verbatim = verbatim
}
public var dictionary: [String: Any] {
var composition = [String: Any]()
composition["type"] = type.rawValue
composition["text"] = text
composition["emoji"] = emoji
composition["verbatim"] = verbatim
return composition
}
}
/// Defined by https://api.slack.com/reference/messaging/composition-objects#option
public struct OptionComposition {
public let text: TextComposition
public let value: String
public let url: String?
public init(text: TextComposition,
value: String,
url: String? = nil) {
self.text = text
self.value = value
self.url = url
}
public var dictionary: [String: Any] {
var composition = [String: Any]()
composition["text"] = text.dictionary
composition["value"] = value
composition["url"] = url
return composition
}
}
/// Defined by https://api.slack.com/reference/messaging/composition-objects#option-group
public struct OptionGroupComposition {
public let label: TextComposition
public let options: [OptionComposition]
public init(label: TextComposition,
options: [OptionComposition]) {
self.label = label
self.options = options
}
public var dictionary: [String: Any] {
var composition = [String: Any]()
composition["label"] = label.dictionary
composition["options"] = options.map { $0.dictionary }
return composition
}
}
/// Defined by https://api.slack.com/reference/messaging/composition-objects#confirm
public struct ConfirmComposition {
public let title: TextComposition
public let text: TextComposition
public let confirm: TextComposition
public let deny: TextComposition
public init(title: TextComposition,
text: TextComposition,
confirm: TextComposition,
deny: TextComposition) {
self.title = title
self.text = text
self.confirm = confirm
self.deny = deny
}
public var dictionary: [String: Any] {
var composition = [String: Any]()
composition["title"] = title.dictionary
composition["text"] = text.dictionary
composition["confirm"] = confirm.dictionary
composition["deny"] = deny.dictionary
return composition
}
}
+354
View File
@@ -0,0 +1,354 @@
public protocol BlockElement {
var dictionary: [String: Any] { get }
}
public protocol SectionElement: BlockElement {}
public protocol ActionsElement: BlockElement {}
public protocol ContextElement: BlockElement {}
/// Defined by https://api.slack.com/reference/messaging/block-elements#image
public struct ImageElement: BlockElement, SectionElement, ContextElement {
/// Type will always be image.
public let type: BlockType
public let imageURL: String
public let altText: String
public init(type: BlockType = .image,
imageURL: String,
altText: String) {
self.type = type
self.imageURL = imageURL
self.altText = altText
}
public var dictionary: [String: Any] {
var element = [String: Any]()
element["type"] = type.rawValue
element["image_url"] = imageURL
element["alt_text"] = altText
return element
}
}
/// Custom type to better support Context PlainText
public struct PlainTextElement: BlockElement, ContextElement {
/// Type will always be image.
public let type: BlockType
public let text: String
public init(type: BlockType = .plainText,
text: String) {
self.type = type
self.text = text
}
public var dictionary: [String: Any] {
var element = [String: Any]()
element["type"] = type.rawValue
element["text"] = text
return element
}
}
/// Custom type to better support Context Markdown Text
public struct MarkdownTextElement: BlockElement, ContextElement {
/// Type will always be image.
public let type: BlockType
public let text: String
public init(type: BlockType = .markdown,
text: String) {
self.type = type
self.text = text
}
public var dictionary: [String: Any] {
var element = [String: Any]()
element["type"] = type.rawValue
element["text"] = text
return element
}
}
public enum ButtonElementStyle: String, CaseIterable {
case `default`
case primary
case danger
}
/// Defined by https://api.slack.com/reference/messaging/block-elements#button
public struct ButtonElement: BlockElement, SectionElement, ActionsElement {
/// Type will always be button.
public let type: BlockType
public let text: TextComposition
public let actionId: String
public let url: String?
public let value: String?
public let style: ButtonElementStyle?
public init(type: BlockType = .button,
text: TextComposition,
actionId: String,
url: String? = nil,
value: String? = nil,
style: ButtonElementStyle? = nil) {
self.type = type
self.text = text
self.actionId = actionId
self.url = url
self.value = value
self.style = style
}
public var dictionary: [String: Any] {
var element = [String: Any]()
element["type"] = type.rawValue
element["text"] = text.dictionary
element["action_id"] = actionId
element["url"] = url
element["value"] = value
element["style"] = style
return element
}
}
/// Defined by https://api.slack.com/reference/messaging/block-elements#static-select
public struct StaticSelectElement: BlockElement, SectionElement, ActionsElement {
/// Type will always be static_select.
public let type: BlockType
public let placeholder: TextComposition
public let actionId: String
public let options: [OptionComposition]?
public let optionGroups: [OptionGroupComposition]?
public let initialOption: OptionComposition?
public let initialOptionGroup: OptionGroupComposition?
public let confirm: ConfirmComposition?
public init(type: BlockType = .staticSelect,
placeholder: TextComposition,
actionId: String,
options: [OptionComposition]? = nil,
optionGroups: [OptionGroupComposition]? = nil,
initialOption: OptionComposition? = nil,
initialOptionGroup: OptionGroupComposition? = nil,
confirm: ConfirmComposition? = nil) {
self.type = type
self.placeholder = placeholder
self.actionId = actionId
self.options = options
self.optionGroups = optionGroups
self.initialOption = initialOption
self.initialOptionGroup = initialOptionGroup
self.confirm = confirm
}
public var dictionary: [String: Any] {
var element = [String: Any]()
element["type"] = type.rawValue
element["placeholder"] = placeholder.dictionary
element["action_id"] = actionId
element["options"] = options?.map { $0.dictionary }
element["option_groups"] = optionGroups?.map { $0.dictionary }
element["initial_option"] = initialOption?.dictionary ?? initialOptionGroup?.dictionary
element["confirm"] = confirm?.dictionary
return element
}
}
/// Defined by https://api.slack.com/reference/messaging/block-elements#external-select
public struct ExternalSelectElement: BlockElement, SectionElement, ActionsElement {
/// Type will always be externalSelect.
public let type: BlockType
public let placeholder: TextComposition
public let actionId: String
public let initialOption: OptionComposition?
public let initialOptionGroup: OptionGroupComposition?
public let minQueryLenght: Int?
public let confirm: ConfirmComposition?
public init(type: BlockType = .externalSelect,
placeholder: TextComposition,
actionId: String,
initialOption: OptionComposition? = nil,
initialOptionGroup: OptionGroupComposition? = nil,
minQueryLenght: Int?,
confirm: ConfirmComposition? = nil) {
self.type = type
self.placeholder = placeholder
self.actionId = actionId
self.initialOption = initialOption
self.initialOptionGroup = initialOptionGroup
self.minQueryLenght = minQueryLenght
self.confirm = confirm
}
public var dictionary: [String: Any] {
var element = [String: Any]()
element["type"] = type.rawValue
element["placeholder"] = placeholder.dictionary
element["action_id"] = actionId
element["initial_option"] = initialOption?.dictionary ?? initialOptionGroup?.dictionary
element["min_query_length"] = minQueryLenght
element["confirm"] = confirm?.dictionary
return element
}
}
/// Defined by https://api.slack.com/reference/messaging/block-elements#user-select
public struct UsersSelectElement: BlockElement, SectionElement, ActionsElement {
/// Type will always be usersSelect.
public let type: BlockType
public let placeholder: TextComposition
public let actionId: String
public let initialUserId: String?
public let confirm: ConfirmComposition?
public init(type: BlockType = .usersSelect,
placeholder: TextComposition,
actionId: String,
initialUserId: String?,
confirm: ConfirmComposition? = nil) {
self.type = type
self.placeholder = placeholder
self.actionId = actionId
self.initialUserId = initialUserId
self.confirm = confirm
}
public var dictionary: [String: Any] {
var element = [String: Any]()
element["type"] = type.rawValue
element["placeholder"] = placeholder.dictionary
element["action_id"] = actionId
element["initial_user"] = initialUserId
element["confirm"] = confirm?.dictionary
return element
}
}
/// Defined by https://api.slack.com/reference/messaging/block-elements#converstation-select
public struct ConverstationSelectElement: BlockElement, SectionElement, ActionsElement {
/// Type will always be converstationSelect.
public let type: BlockType
public let placeholder: TextComposition
public let actionId: String
public let initialConverstationId: String?
public let confirm: ConfirmComposition?
public init(type: BlockType = .converstationSelect,
placeholder: TextComposition,
actionId: String,
initialConverstationId: String?,
confirm: ConfirmComposition? = nil) {
self.type = type
self.placeholder = placeholder
self.actionId = actionId
self.initialConverstationId = initialConverstationId
self.confirm = confirm
}
public var dictionary: [String: Any] {
var element = [String: Any]()
element["type"] = type.rawValue
element["placeholder"] = placeholder.dictionary
element["action_id"] = actionId
element["initial_conversation"] = initialConverstationId
element["confirm"] = confirm?.dictionary
return element
}
}
/// Defined by https://api.slack.com/reference/messaging/block-elements#channel-select
public struct ChannelSelectElement: BlockElement, SectionElement, ActionsElement {
/// Type will always be channelSelect.
public let type: BlockType
public let placeholder: TextComposition
public let actionId: String
public let initialChannelId: String?
public let confirm: ConfirmComposition?
public init(type: BlockType = .channelSelect,
placeholder: TextComposition,
actionId: String,
initialChannelId: String?,
confirm: ConfirmComposition? = nil) {
self.type = type
self.placeholder = placeholder
self.actionId = actionId
self.initialChannelId = initialChannelId
self.confirm = confirm
}
public var dictionary: [String: Any] {
var element = [String: Any]()
element["type"] = type.rawValue
element["placeholder"] = placeholder.dictionary
element["action_id"] = actionId
element["initial_channel"] = initialChannelId
element["confirm"] = confirm?.dictionary
return element
}
}
/// Defined by https://api.slack.com/reference/messaging/block-elements#overflow
public struct OverflowElement: BlockElement, SectionElement, ActionsElement {
/// Type will always be overflow.
public let type: BlockType
public let actionId: String
public let options: [OptionComposition]
public let confirm: ConfirmComposition?
public init(type: BlockType = .overflow,
placeholder: TextComposition,
actionId: String,
options: [OptionComposition],
confirm: ConfirmComposition? = nil) {
self.type = type
self.actionId = actionId
self.options = options
self.confirm = confirm
}
public var dictionary: [String: Any] {
var element = [String: Any]()
element["type"] = type.rawValue
element["action_id"] = actionId
element["options"] = options
element["confirm"] = confirm?.dictionary
return element
}
}
/// Defined by https://api.slack.com/reference/messaging/block-elements#overflow
public struct DatePickerElement: BlockElement, SectionElement, ActionsElement {
/// Type will always be datePicker.
public let type: BlockType
public let actionId: String
public let placeholder: TextComposition?
public let initialDate: String?
public let confirm: ConfirmComposition?
public init(type: BlockType = .datePicker,
actionId: String,
placeholder: TextComposition?,
initialDate: String? = nil,
confirm: ConfirmComposition? = nil) {
self.type = type
self.actionId = actionId
self.placeholder = placeholder
self.initialDate = initialDate
self.confirm = confirm
}
public var dictionary: [String: Any] {
var element = [String: Any]()
element["type"] = type.rawValue
element["action_id"] = actionId
element["placeholder"] = placeholder?.dictionary
element["initial_date"] = initialDate
element["confirm"] = confirm?.dictionary
return element
}
}
+156
View File
@@ -0,0 +1,156 @@
public protocol Block {
var dictionary: [String: Any] { get }
}
public enum BlockType: String, CaseIterable {
case actions
case button
case context
case datePicker = "datepicker"
case divider
case image
case markdown = "mrkdwn"
case overflow
case plainText = "plain_text"
case section
// Selects
case channelSelect = "channels_select"
case converstationSelect = "conversations_select"
case externalSelect = "external_select"
case staticSelect = "static_select"
case usersSelect = "users_select"
}
/// Defined by https://api.slack.com/reference/messaging/blocks#section
public struct SectionBlock: Block {
/// Type will always be section.
public let type: BlockType
public let text: TextComposition
public let blockId: String?
public let fields: [TextComposition]?
public let accessory: SectionElement?
public init(type: BlockType = .section,
text: TextComposition,
blockId: String? = nil,
fields: [TextComposition]? = nil,
accessory: SectionElement? = nil) {
self.type = type
self.text = text
self.blockId = blockId
self.fields = fields
self.accessory = accessory
}
public var dictionary: [String: Any] {
var block = [String: Any]()
block["type"] = type.rawValue
block["text"] = text.dictionary
block["block_id"] = blockId
block["fields"] = fields?.map { $0.dictionary }
block["accessory"] = accessory?.dictionary
return block
}
}
/// Defined by https://api.slack.com/reference/messaging/blocks#divider
public struct DividerBlock: Block {
/// Type will always be divider.
public let type: BlockType
public let blockId: String?
public init(type: BlockType = .divider,
blockId: String? = nil) {
self.type = type
self.blockId = blockId
}
public var dictionary: [String: Any] {
var block = [String: Any]()
block["type"] = type
block["block_id"] = blockId
return block
}
}
/// Defined by https://api.slack.com/reference/messaging/blocks#image
public struct ImageBlock: Block {
/// Type will always be image.
public let type: BlockType
public let imageURL: String
public let altText: String
public let title: String?
public let blockId: String?
public init(type: BlockType = .image,
imageURL: String,
altText: String,
title: String? = nil,
blockId: String? = nil) {
self.type = type
self.imageURL = imageURL
self.altText = altText
self.title = title
self.blockId = blockId
}
public var dictionary: [String: Any] {
var block = [String: Any]()
block["type"] = type.rawValue
block["image_url"] = imageURL
block["alt_text"] = altText
block["title"] = title
block["block_id"] = blockId
return block
}
}
/// Defined by https://api.slack.com/reference/messaging/blocks#actions
public struct ActionsBlock: Block {
/// Type will always be actions.
public let type: BlockType
public let elements: [ActionsElement]
public let blockId: String?
public init(type: BlockType = .actions,
elements: [ActionsElement],
blockId: String? = nil) {
self.type = type
self.elements = elements
self.blockId = blockId
}
public var dictionary: [String: Any] {
var block = [String: Any]()
block["type"] = type.rawValue
block["elements"] = elements.map { $0.dictionary }
block["block_id"] = blockId
return block
}
}
/// Defined by https://api.slack.com/reference/messaging/blocks#context
public struct ContextBlock: Block {
/// Type will always be actions.
public let type: BlockType
public let elements: [ContextElement]
public let blockId: String?
public init(type: BlockType = .context,
elements: [ContextElement],
blockId: String? = nil) {
self.type = type
self.elements = elements
self.blockId = blockId
}
public var dictionary: [String: Any] {
var block = [String: Any]()
block["type"] = type.rawValue
block["elements"] = elements.map { $0.dictionary }
block["block_id"] = blockId
return block
}
}
+23 -2
View File
@@ -22,11 +22,32 @@
// THE SOFTWARE.
public struct Edited {
fileprivate enum CodingKeys: String {
case user
case ts
}
public let user: String?
public let ts: String?
public init(edited: [String: Any]?) {
user = edited?["user"] as? String
ts = edited?["ts"] as? String
user = edited?[CodingKeys.user.rawValue] as? String
ts = edited?[CodingKeys.ts.rawValue] as? String
}
}
extension Edited: Codable {
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
user = try values.decodeIfPresent(String.self, forKey: .user)
ts = try values.decodeIfPresent(String.self, forKey: .ts)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(user, forKey: .user)
try container.encode(ts, forKey: .ts)
}
}
extension Edited.CodingKeys: CodingKey { }
+24 -3
View File
@@ -22,11 +22,32 @@
// THE SOFTWARE.
public struct Reply {
fileprivate enum CodingKeys: String {
case user
case ts
}
public let user: String?
public let ts: String?
public init(reply: [String: Any]?) {
user = reply?["user"] as? String
ts = reply?["ts"] as? String
user = reply?[CodingKeys.user.rawValue] as? String
ts = reply?[CodingKeys.ts.rawValue] as? String
}
}
extension Reply: Codable {
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
user = try values.decodeIfPresent(String.self, forKey: .user)
ts = try values.decodeIfPresent(String.self, forKey: .ts)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(user, forKey: .user)
try container.encode(ts, forKey: .ts)
}
}
extension Reply.CodingKeys: CodingKey { }
+1
View File
@@ -88,6 +88,7 @@ public enum Endpoint: String {
case usersGetPresence = "users.getPresence"
case usersInfo = "users.info"
case usersList = "users.list"
case usersLookupByEmail = "users.lookupByEmail"
case usersProfileSet = "users.profile.set"
case usersSetActive = "users.setActive"
case usersSetPresence = "users.setPresence"
+27 -1
View File
@@ -271,6 +271,7 @@ extension WebAPI {
parse: ParseMode? = nil,
linkNames: Bool? = nil,
attachments: [Attachment?]? = nil,
blocks: [Block]? = nil,
unfurlLinks: Bool? = nil,
unfurlMedia: Bool? = nil,
iconURL: String? = nil,
@@ -290,7 +291,8 @@ extension WebAPI {
"username": username,
"icon_url": iconURL,
"icon_emoji": iconEmoji,
"attachments": encodeAttachments(attachments)
"attachments": encodeAttachments(attachments),
"blocks": encodeBlocks(blocks)
]
networkInterface.request(.chatPostMessage, parameters: parameters, successClosure: {(response) in
success?((ts: response["ts"] as? String, response["channel"] as? String))
@@ -346,6 +348,7 @@ extension WebAPI {
thread: String? = nil,
asUser: Bool? = nil,
attachments: [Attachment?]? = nil,
blocks: [Block]? = nil,
linkNames: Bool? = nil,
parse: ParseMode? = nil,
success: (((ts: String?, channel: String?)) -> Void)?,
@@ -359,6 +362,7 @@ extension WebAPI {
"thread_ts": thread,
"as_user": asUser,
"attachments": encodeAttachments(attachments),
"blocks": encodeBlocks(blocks),
"link_names": linkNames,
"parse": parse?.rawValue,
]
@@ -1134,6 +1138,15 @@ extension WebAPI {
failure?(error)
}
}
public func usersLookupByEmail(_ email: String, success: ((_ user: User) -> Void)?, failure: FailureClosure?) {
let parameters: [String: Any] = ["token": token, "email": email]
networkInterface.request(.usersLookupByEmail, parameters: parameters, successClosure: { response in
success?(User(user: response["user"] as? [String: Any]))
}) { error in
failure?(error)
}
}
public func usersProfileSet(profile: User.Profile, success: SuccessClosure?, failure: FailureClosure?) {
let profileValues = ([
@@ -1241,6 +1254,19 @@ extension WebAPI {
return nil
}
fileprivate func encodeBlocks(_ blocks: [Block]?) -> String? {
if let blocks = blocks {
let blocksArray: [[String: Any]] = blocks.map { $0.dictionary }
do {
let data = try JSONSerialization.data(withJSONObject: blocksArray, options: [])
return String(data: data, encoding: String.Encoding.utf8)
} catch let error {
print(error)
}
}
return nil
}
fileprivate func enumerateDNDStatuses(_ statuses: [String: Any]) -> [String: DoNotDisturbStatus] {
var retVal = [String: DoNotDisturbStatus]()
for key in statuses.keys {
+28
View File
@@ -119,6 +119,13 @@
26D4E6082212120900A67B67 /* SKRTMAPI.h in Headers */ = {isa = PBXBuildFile; fileRef = 26D4E5FE221211B900A67B67 /* SKRTMAPI.h */; settings = {ATTRIBUTES = (Public, ); }; };
26D4E6092212120F00A67B67 /* SKServer.h in Headers */ = {isa = PBXBuildFile; fileRef = 26D4E600221211B900A67B67 /* SKServer.h */; settings = {ATTRIBUTES = (Public, ); }; };
26D4E60A2212121400A67B67 /* SKWebAPI.h in Headers */ = {isa = PBXBuildFile; fileRef = 26D4E601221211B900A67B67 /* SKWebAPI.h */; settings = {ATTRIBUTES = (Public, ); }; };
A20D15EA22DE158000044CFC /* BlockComposition.swift in Sources */ = {isa = PBXBuildFile; fileRef = A20D15E722DE158000044CFC /* BlockComposition.swift */; };
A20D15EB22DE158000044CFC /* BlockElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = A20D15E822DE158000044CFC /* BlockElement.swift */; };
A20D15EC22DE158000044CFC /* BlockLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = A20D15E922DE158000044CFC /* BlockLayout.swift */; };
9EA45FB922C01290006A6D36 /* action.json in Resources */ = {isa = PBXBuildFile; fileRef = 9EA45FB822C01290006A6D36 /* action.json */; };
9EE6A7C322C2CDD6002BD111 /* edited.json in Resources */ = {isa = PBXBuildFile; fileRef = 9EE6A7C222C2CDD6002BD111 /* edited.json */; };
9EEC459622BE63F800206AC3 /* reply.json in Resources */ = {isa = PBXBuildFile; fileRef = 9EEC459522BE63F800206AC3 /* reply.json */; };
9EEC459822BE789600206AC3 /* attachmentfield.json in Resources */ = {isa = PBXBuildFile; fileRef = 9EEC459722BE789600206AC3 /* attachmentfield.json */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -311,6 +318,13 @@
26D4E6292220731800A67B67 /* rtm.connect.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = rtm.connect.json; sourceTree = "<group>"; };
26D4E62A2220731800A67B67 /* file.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = file.json; sourceTree = "<group>"; };
26D4E6362220733F00A67B67 /* SKCoreTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SKCoreTests.swift; sourceTree = "<group>"; };
A20D15E722DE158000044CFC /* BlockComposition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockComposition.swift; sourceTree = "<group>"; };
A20D15E822DE158000044CFC /* BlockElement.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockElement.swift; sourceTree = "<group>"; };
A20D15E922DE158000044CFC /* BlockLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockLayout.swift; sourceTree = "<group>"; };
9EA45FB822C01290006A6D36 /* action.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = action.json; sourceTree = "<group>"; };
9EE6A7C222C2CDD6002BD111 /* edited.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = edited.json; sourceTree = "<group>"; };
9EEC459522BE63F800206AC3 /* reply.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = reply.json; sourceTree = "<group>"; };
9EEC459722BE789600206AC3 /* attachmentfield.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = attachmentfield.json; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -494,6 +508,9 @@
263B102221FE33A000AF9EF9 /* Action.swift */,
263B100C21FE33A000AF9EF9 /* Attachment.swift */,
263B100B21FE33A000AF9EF9 /* AttachmentField.swift */,
A20D15E722DE158000044CFC /* BlockComposition.swift */,
A20D15E822DE158000044CFC /* BlockElement.swift */,
A20D15E922DE158000044CFC /* BlockLayout.swift */,
263B101B21FE33A000AF9EF9 /* Bot.swift */,
263B101521FE33A000AF9EF9 /* Channel.swift */,
263B101021FE33A000AF9EF9 /* Comment.swift */,
@@ -818,13 +835,17 @@
26D4E618222072A700A67B67 /* Resources */ = {
isa = PBXGroup;
children = (
9EA45FB822C01290006A6D36 /* action.json */,
9EEC459722BE789600206AC3 /* attachmentfield.json */,
26D4E6282220731800A67B67 /* channel.json */,
26D4E6222220731700A67B67 /* conversation.json */,
9EE6A7C222C2CDD6002BD111 /* edited.json */,
26D4E6262220731800A67B67 /* events.json */,
26D4E62A2220731800A67B67 /* file.json */,
26D4E6272220731800A67B67 /* group.json */,
26D4E6232220731700A67B67 /* im.json */,
26D4E6212220731700A67B67 /* mpim.json */,
9EEC459522BE63F800206AC3 /* reply.json */,
26D4E6292220731800A67B67 /* rtm.connect.json */,
26D4E6202220731700A67B67 /* rtm.start.json */,
26D4E6242220731800A67B67 /* user.json */,
@@ -1185,6 +1206,7 @@
buildActionMask = 2147483647;
files = (
2601B6CD2223038A00F197AB /* channel.json in Resources */,
9EA45FB922C01290006A6D36 /* action.json in Resources */,
2601B710222F766D00F197AB /* member_left_channel.json in Resources */,
2601B6CE2223038A00F197AB /* conversation.json in Resources */,
2601B70F222F766D00F197AB /* member_joined_channel.json in Resources */,
@@ -1195,8 +1217,11 @@
2601B6D32223038A00F197AB /* mpim.json in Resources */,
2601B6D42223038A00F197AB /* rtm.connect.json in Resources */,
2601B6D52223038A00F197AB /* rtm.start.json in Resources */,
9EEC459822BE789600206AC3 /* attachmentfield.json in Resources */,
2601B6D62223038A00F197AB /* user.json in Resources */,
9EEC459622BE63F800206AC3 /* reply.json in Resources */,
2601B6D72223038A00F197AB /* usergroup.json in Resources */,
9EE6A7C322C2CDD6002BD111 /* edited.json in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1262,6 +1287,8 @@
263B103D21FE33A000AF9EF9 /* Action.swift in Sources */,
263B102D21FE33A000AF9EF9 /* Item.swift in Sources */,
263B103521FE33A000AF9EF9 /* Extensions.swift in Sources */,
A20D15EA22DE158000044CFC /* BlockComposition.swift in Sources */,
A20D15EB22DE158000044CFC /* BlockElement.swift in Sources */,
263B103621FE33A000AF9EF9 /* Bot.swift in Sources */,
263B102921FE33A000AF9EF9 /* Edited.swift in Sources */,
263B102721FE33A000AF9EF9 /* Attachment.swift in Sources */,
@@ -1269,6 +1296,7 @@
263B103C21FE33A000AF9EF9 /* SlackError.swift in Sources */,
263B103221FE33A000AF9EF9 /* Team.swift in Sources */,
263B103321FE33A000AF9EF9 /* User.swift in Sources */,
A20D15EC22DE158000044CFC /* BlockLayout.swift in Sources */,
263B103021FE33A000AF9EF9 /* Channel.swift in Sources */,
263B102621FE33A000AF9EF9 /* AttachmentField.swift in Sources */,
263B102B21FE33A000AF9EF9 /* Comment.swift in Sources */,
+27
View File
@@ -0,0 +1,27 @@
{
"name": "any name",
"text": "any text",
"type": "any type",
"value": "any value",
"url": "any url",
"style": "primary",
"confirm":
{
"title": "any title",
"text": "any text",
"ok_text": "any ok text",
"dismiss_text": "any dismiss text"
},
"options":
[
{
"text": "any text 1",
"value": "any value 1"
},
{
"text": "any text 2",
"value": "any value 2"
}
],
"data_source": "channels"
}
@@ -0,0 +1,5 @@
{
"title": "any title",
"value": "any value",
"short": false
}
+4
View File
@@ -0,0 +1,4 @@
{
"user": "U0CJ5PC7L",
"ts": "1448262357.000002"
}
+4
View File
@@ -0,0 +1,4 @@
{
"user": "U0CJ5PC7L",
"ts": "1448262357.000002"
}
+91 -1
View File
@@ -44,6 +44,10 @@ final class SKCoreTests: XCTestCase {
static let user = try! Data(contentsOf: URL(fileURLWithPath: "\(rootPath)/user.json"))
static let usergroup = try! Data(contentsOf: URL(fileURLWithPath: "\(rootPath)/usergroup.json"))
static let events = try! Data(contentsOf: URL(fileURLWithPath: "\(rootPath)/events.json"))
static let action = try! Data(contentsOf: URL(fileURLWithPath: "\(rootPath)/action.json"))
static let attachmentfield = try! Data(contentsOf: URL(fileURLWithPath: "\(rootPath)/attachmentfield.json"))
static let edited = try! Data(contentsOf: URL(fileURLWithPath: "\(rootPath)/edited.json"))
static let reply = try! Data(contentsOf: URL(fileURLWithPath: "\(rootPath)/reply.json"))
}
static var allTests = [
@@ -55,7 +59,10 @@ final class SKCoreTests: XCTestCase {
("TestMpim", testMpim),
("testUser", testUser),
("testUserGroup", testUserGroup),
("testEvents", testEvents)
("testEvents", testEvents),
("testActionCodable", testActionCodable),
("testAttachmentFieldCodable", testAttachmentFieldCodable),
("testReplyCodable", testReplyCodable)
]
func testEvents() {
@@ -146,4 +153,87 @@ final class SKCoreTests: XCTestCase {
let userGroup = UserGroup(userGroup: json)
XCTAssertNotNil(userGroup)
}
func testActionCodable() {
let data = JSONData.action
let decoder = JSONDecoder()
let actionByDecoder = try? decoder.decode(Action.self, from: data)
XCTAssertNotNil(actionByDecoder)
XCTAssertNotNil(actionByDecoder!.name)
XCTAssertNotNil(actionByDecoder!.text)
XCTAssertNotNil(actionByDecoder!.type)
XCTAssertNotNil(actionByDecoder!.value)
XCTAssertNotNil(actionByDecoder!.url)
XCTAssertNotNil(actionByDecoder!.style)
XCTAssertNotNil(actionByDecoder!.confirm)
XCTAssertNotNil(actionByDecoder!.options)
XCTAssertNotNil(actionByDecoder!.dataSource)
let encoder = JSONEncoder()
let jsonData = try? encoder.encode(actionByDecoder!)
XCTAssertNotNil(jsonData)
let action = try? JSONSerialization.jsonObject(with: jsonData!, options: []) as? [String: Any]
XCTAssertNotNil(action)
let actionBySerialization = Action(action: action)
XCTAssertEqual(actionBySerialization.name, actionByDecoder!.name)
XCTAssertEqual(actionBySerialization.text, actionByDecoder!.text)
XCTAssertEqual(actionBySerialization.type, actionByDecoder!.type)
XCTAssertEqual(actionBySerialization.value, actionByDecoder!.value)
XCTAssertEqual(actionBySerialization.url, actionByDecoder!.url)
XCTAssertEqual(actionBySerialization.style, actionByDecoder!.style)
XCTAssertEqual(actionBySerialization.options?.count, actionByDecoder!.options?.count)
XCTAssertEqual(actionBySerialization.dataSource, actionByDecoder!.dataSource)
}
func testAttachmentFieldCodable() {
let data = JSONData.attachmentfield
let decoder = JSONDecoder()
let attachmentFieldByDecoder = try? decoder.decode(AttachmentField.self, from: data)
XCTAssertNotNil(attachmentFieldByDecoder)
XCTAssertNotNil(attachmentFieldByDecoder!.title)
XCTAssertNotNil(attachmentFieldByDecoder!.value)
XCTAssertNotNil(attachmentFieldByDecoder!.short)
let encoder = JSONEncoder()
let jsonData = try? encoder.encode(attachmentFieldByDecoder!)
XCTAssertNotNil(jsonData)
let field = try? JSONSerialization.jsonObject(with: jsonData!, options: []) as? [String: Any]
XCTAssertNotNil(field)
let attachmentFieldBySerialization = AttachmentField(field: field)
XCTAssertEqual(attachmentFieldBySerialization.title, attachmentFieldByDecoder!.title)
XCTAssertEqual(attachmentFieldBySerialization.value, attachmentFieldByDecoder!.value)
XCTAssertEqual(attachmentFieldBySerialization.short, attachmentFieldByDecoder!.short)
}
func testEditedCodable() {
let data = JSONData.edited
let decoder = JSONDecoder()
let editedByDecoder = try? decoder.decode(Edited.self, from: data)
XCTAssertNotNil(editedByDecoder)
XCTAssertNotNil(editedByDecoder!.user)
XCTAssertNotNil(editedByDecoder!.ts)
let encoder = JSONEncoder()
let jsonData = try? encoder.encode(editedByDecoder!)
XCTAssertNotNil(jsonData)
let edited = try? JSONSerialization.jsonObject(with: jsonData!, options: []) as? [String: Any]
XCTAssertNotNil(edited)
let editedBySerialization = Edited(edited: edited)
XCTAssertEqual(editedBySerialization.user, editedByDecoder!.user)
XCTAssertEqual(editedBySerialization.ts, editedByDecoder!.ts)
}
func testReplyCodable() {
let data = JSONData.reply
let decoder = JSONDecoder()
let replyByDecoder = try? decoder.decode(Reply.self, from: data)
XCTAssertNotNil(replyByDecoder)
XCTAssertNotNil(replyByDecoder!.user)
XCTAssertNotNil(replyByDecoder!.ts)
let encoder = JSONEncoder()
let jsonData = try? encoder.encode(replyByDecoder!)
XCTAssertNotNil(jsonData)
let reply = try? JSONSerialization.jsonObject(with: jsonData!, options: []) as? [String: Any]
XCTAssertNotNil(reply)
let replyBySerialization = Reply(reply: reply)
XCTAssertEqual(replyBySerialization.user, replyByDecoder!.user)
XCTAssertEqual(replyBySerialization.ts, replyByDecoder!.ts)
}
}
+4
View File
@@ -18,6 +18,10 @@ extension SKCoreTests {
("testMpim", testMpim),
("testUser", testUser),
("testUserGroup", testUserGroup),
("testActionCodable", testActionCodable),
("testAttachmentFieldCodable", testAttachmentFieldCodable),
("testEditedCodable", testEditedCodable),
("testReplyCodable", testReplyCodable),
]
}
+15 -15
View File
@@ -10,7 +10,7 @@ jobs:
# Builds
- job: macOS
pool:
vmImage: 'macOS-10.13'
vmImage: 'macOS-10.14'
steps:
- task: Xcode@5
inputs:
@@ -18,19 +18,19 @@ jobs:
scheme: 'SlackKit'
sdk: 'macosx'
configuration: 'Release'
xcWorkspacePath: '**/*.xcodeproj/project.xcworkspace'
xcodeVersion: '10'
xcWorkspacePath: 'SlackKit.xcodeproj/project.xcworkspace'
xcodeVersion: 'default'
- task: Xcode@5
inputs:
actions: 'test'
scheme: 'SlackKitTests'
sdk: 'macosx'
configuration: 'Debug'
xcWorkspacePath: '**/*.xcodeproj/project.xcworkspace'
xcodeVersion: '10'
xcWorkspacePath: 'SlackKit.xcodeproj/project.xcworkspace'
xcodeVersion: 'default'
- job: iOS
pool:
vmImage: 'macOS-10.13'
vmImage: 'macOS-10.14'
steps:
- task: Xcode@5
inputs:
@@ -38,19 +38,19 @@ jobs:
scheme: 'SlackKit'
sdk: 'iphoneos'
configuration: 'Release'
xcWorkspacePath: '**/*.xcodeproj/project.xcworkspace'
xcodeVersion: '10'
xcWorkspacePath: 'SlackKit.xcodeproj/project.xcworkspace'
xcodeVersion: 'default'
- task: Xcode@5
inputs:
actions: 'test'
scheme: 'SlackKitTests'
sdk: 'iphonesimulator'
configuration: 'Debug'
xcWorkspacePath: '**/*.xcodeproj/project.xcworkspace'
xcodeVersion: '10'
xcWorkspacePath: 'SlackKit.xcodeproj/project.xcworkspace'
xcodeVersion: 'default'
- job: tvOS
pool:
vmImage: 'macOS-10.13'
vmImage: 'macOS-10.14'
steps:
- task: Xcode@5
inputs:
@@ -58,16 +58,16 @@ jobs:
scheme: 'SlackKit'
sdk: 'appletvos'
configuration: 'Release'
xcWorkspacePath: '**/*.xcodeproj/project.xcworkspace'
xcodeVersion: '10'
xcWorkspacePath: 'SlackKit.xcodeproj/project.xcworkspace'
xcodeVersion: 'default'
- task: Xcode@5
inputs:
actions: 'test'
scheme: 'SlackKitTests'
sdk: 'appletvsimulator'
configuration: 'Debug'
xcWorkspacePath: '**/*.xcodeproj/project.xcworkspace'
xcodeVersion: '10'
xcWorkspacePath: 'SlackKit.xcodeproj/project.xcworkspace'
xcodeVersion: 'default'
- job: Linux
pool:
vmImage: 'ubuntu-16.04'