govuk_taxonomy_helpers 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4a9bcf516b96e7dcec97d7c83fc3915100922700
4
+ data.tar.gz: ded38f9b3e0d1602a22b4d3d1e498995a65048a9
5
+ SHA512:
6
+ metadata.gz: 2a7bbbb176477e4d4c7611c900f4df8830579cc9b2289b93cb18b3a39bf397c2cfca51e57b3ef3c274cc1140fb4548a83e5912a9e85a22f34312593e4c3b6bdd
7
+ data.tar.gz: 2730337dfffb046d85fb25c84c728285bdd111245e3758ff7e0188dd87d2545936cfab48e3e7c4a7f4af3363e897e84cd673e14f43ce72c80b3b785c34f52642
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,12 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.3
3
+
4
+ # Method documentation is required to ensure the gem is fully documented
5
+ Style/DocumentationMethod:
6
+ Enabled: true
7
+
8
+ Style/VariableNumber:
9
+ Enabled: false
10
+
11
+ Style/SafeNavigation:
12
+ Enabled: false
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.4.0
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ --markup markdown
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ## 0.0.1
2
+
3
+ * Gem created
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in govuk_taxonomy_helpers.gemspec
4
+ gemspec
data/Jenkinsfile ADDED
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env groovy
2
+
3
+ REPOSITORY = 'govuk_taxonomy_helpers'
4
+
5
+ node {
6
+ def govuk = load '/var/lib/jenkins/groovy_scripts/govuk_jenkinslib.groovy'
7
+
8
+ try {
9
+ stage('Checkout') {
10
+ checkout scm
11
+ }
12
+
13
+ stage('Clean') {
14
+ govuk.cleanupGit()
15
+ govuk.mergeMasterBranch()
16
+ }
17
+
18
+ stage('Bundle') {
19
+ govuk.bundleGem()
20
+ }
21
+
22
+ stage('Linter') {
23
+ govuk.rubyLinter()
24
+ }
25
+
26
+ stage('Tests') {
27
+ govuk.runTests('spec')
28
+ }
29
+
30
+ if(env.BRANCH_NAME == "master") {
31
+ stage('Publish Gem') {
32
+ govuk.publishGem(REPOSITORY, env.BRANCH_NAME)
33
+ }
34
+ }
35
+
36
+ } catch (e) {
37
+ currentBuild.result = 'FAILED'
38
+ step([$class: 'Mailer',
39
+ notifyEveryUnstableBuild: true,
40
+ recipients: 'govuk-ci-notifications@digital.cabinet-office.gov.uk',
41
+ sendToIndividuals: true])
42
+ throw e
43
+ }
44
+ }
data/LICENCE.txt ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (C) 2017 Crown copyright (Government Digital Service)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7
+ of the Software, and to permit persons to whom the Software is furnished to do
8
+ so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,90 @@
1
+ # GovukTaxonomyHelpers
2
+
3
+ Parses the taxonomy of GOV.UK into a browseable tree structure.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'govuk_taxonomy_helpers'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install govuk_taxonomy_helpers
20
+
21
+ ## Usage
22
+
23
+ The API is provisional and is likely to change for versions < 1.0.0.
24
+
25
+ To access the taxonomy, first request the content from the [publishing api](https://github.com/alphagov/publishing-api), then parse it to get a `LinkedContentItem` object.
26
+
27
+ ```ruby
28
+ require 'govuk_taxonomy_helpers'
29
+
30
+ content_item = Services.publishing_api.get_content("c75c541-403f-4cb1-9b34-4ddde816a80d")
31
+ expanded_links = Services.publishing_api.get_expanded_links("c75c541-403f-4cb1-9b34-4ddde816a80d")
32
+
33
+ taxonomy = GovukTaxonomyHelpers::LinkedContentItem.from_publishing_api(
34
+ content_item: content_item,
35
+ expanded_links: expanded_links
36
+ )
37
+ ```
38
+
39
+ A `LinkedContentItem` built from a taxon can access all *narrower term* taxons below it and all *broader term* taxons above it.
40
+
41
+ A taxon may have many child taxons, but can only have one or zero parents.
42
+
43
+ ```ruby
44
+ taxon.children
45
+ # => [LinkedContentItem(title: child-1-id, ...), LinkedContentItem(title: child-2, ...)]
46
+
47
+ taxon.parent
48
+ # => LinkedContentItem(title: root, ...)
49
+
50
+ taxon.breadcrumb_trail
51
+ # => [LinkedContentItem(title: root, ...), LinkedContentItem(title: taxon, ...)]
52
+ ```
53
+
54
+ A `LinkedContentItem` built from an content_item that isn't a taxon can access all taxons associated with it.
55
+
56
+ ```ruby
57
+ content_item.taxons
58
+ # => [LinkedContentItem(title: taxon, ...),
59
+ # LinkedContentItem(title: another-taxon, ...)]
60
+
61
+ content_item.taxons_with_ancestors
62
+ # => [LinkedContentItem(title: root, ...),
63
+ # LinkedContentItem(title: taxon-parent, ...),
64
+ # LinkedContentItem(title: taxon, ...),
65
+ # LinkedContentItem(title: another-taxon, ...)]
66
+ ```
67
+
68
+ ## Nomenclature
69
+
70
+ - **Taxonomy**: The hierarchy of topics used to classify content by subject on GOV.UK. Not all content is tagged to the taxonomy. We are rolling out the taxonomy and navigation theme-by-theme.
71
+ - **Taxon**: Any topic within the taxonomy.
72
+ - **Content Item**: A distinct version of a document. A taxon is also a type of content item.
73
+
74
+ ## Documentation
75
+
76
+ To run a Yard server locally to preview documentation, run:
77
+
78
+ $ bundle exec yard server --reload
79
+
80
+ ## Contributing
81
+
82
+ 1. Fork it ( https://github.com/alphagov/govuk_taxonomy_helpers/fork )
83
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
84
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
85
+ 4. Push to the branch (`git push origin my-new-feature`)
86
+ 5. Create a new Pull Request
87
+
88
+ ## License
89
+
90
+ [MIT License](LICENCE.txt)
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'govuk_taxonomy_helpers/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "govuk_taxonomy_helpers"
8
+ spec.version = GovukTaxonomyHelpers::VERSION
9
+ spec.authors = ["Government Digital Service"]
10
+ spec.email = ["govuk-dev@digital.cabinet-office.gov.uk"]
11
+ spec.summary = "Parses the taxonomy of GOV.UK into a browseable tree structure."
12
+ spec.homepage = "https://github.com/alphagov/govuk_taxonomy_helpers"
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency "bundler", "~> 1.6"
21
+ spec.add_development_dependency "rake", "~> 10.0"
22
+
23
+ spec.add_development_dependency "rspec", "~> 3.5"
24
+ spec.add_development_dependency "govuk-lint", "~> 2.0.0"
25
+ spec.add_development_dependency "pry-byebug", "~> 3.4"
26
+ spec.add_development_dependency "yard", "~> 0.9.8"
27
+ end
@@ -0,0 +1,3 @@
1
+ require "govuk_taxonomy_helpers/version"
2
+ require "govuk_taxonomy_helpers/publishing_api_response"
3
+ require "govuk_taxonomy_helpers/linked_content_item"
@@ -0,0 +1,111 @@
1
+ module GovukTaxonomyHelpers
2
+ # A LinkedContentItem can be anything that has a content store representation
3
+ # on GOV.UK.
4
+ #
5
+ # It can be used with "taxon" content items (a topic in the taxonomy) or
6
+ # other document types that link to taxons.
7
+ #
8
+ # Taxon instances can have an optional parent and any number of child taxons.
9
+ class LinkedContentItem
10
+ extend Forwardable
11
+ attr_reader :title, :content_id, :base_path, :children, :internal_name
12
+ attr_accessor :parent
13
+ attr_reader :taxons
14
+ def_delegators :tree, :map, :each
15
+
16
+ # @param title [String] the user facing name for the content item
17
+ # @param base_path [String] the relative URL, starting with a leading "/"
18
+ # @param content_id [UUID] unique identifier of the content item
19
+ # @param internal_name [String] an internal name for the content item
20
+ def initialize(title:, base_path:, content_id:, internal_name: nil)
21
+ @title = title
22
+ @internal_name = internal_name
23
+ @content_id = content_id
24
+ @base_path = base_path
25
+ @children = []
26
+ @taxons = []
27
+ end
28
+
29
+ # Add a LinkedContentItem as a child of this one
30
+ def <<(child_node)
31
+ child_node.parent = self
32
+ @children << child_node
33
+ end
34
+
35
+ # Get taxons in the taxon's branch of the taxonomy.
36
+ #
37
+ # @return [Array] all taxons in this branch of the taxonomy, including the content item itself
38
+ def tree
39
+ return [self] if @children.empty?
40
+
41
+ @children.each_with_object([self]) do |child, tree|
42
+ tree.concat(child.tree)
43
+ end
44
+ end
45
+
46
+
47
+ # Get descendants of a taxon
48
+ #
49
+ # @return [Array] all taxons in this branch of the taxonomy, excluding the content item itself
50
+ def descendants
51
+ tree.tap(&:shift)
52
+ end
53
+
54
+ # Get ancestors of a taxon
55
+ #
56
+ # @return [Array] all taxons in the path from the root of the taxonomy to the parent taxon
57
+ def ancestors
58
+ if parent.nil?
59
+ []
60
+ else
61
+ parent.ancestors + [parent]
62
+ end
63
+ end
64
+
65
+ # Get a breadcrumb trail for a taxon
66
+ #
67
+ # @return [Array] all taxons in the path from the root of the taxonomy to this taxon
68
+ def breadcrumb_trail
69
+ ancestors + [self]
70
+ end
71
+
72
+ # Get all linked taxons and their ancestors
73
+ #
74
+ # @return [Array] all taxons that this content item can be found in
75
+ def taxons_with_ancestors
76
+ taxons.flat_map(&:breadcrumb_trail)
77
+ end
78
+
79
+ # @return [Integer] the number of taxons in this branch of the taxonomy
80
+ def count
81
+ tree.count
82
+ end
83
+
84
+ # @return [Boolean] whether this taxon is the root of its taxonomy
85
+ def root?
86
+ parent.nil?
87
+ end
88
+
89
+ # @return [Integer] the number of taxons between this taxon and the taxonomy root
90
+ def depth
91
+ return 0 if root?
92
+ 1 + parent.depth
93
+ end
94
+
95
+ # Link this content item to a taxon
96
+ #
97
+ # @param taxon_node [LinkedContentItem] A taxon content item
98
+ def add_taxon(taxon_node)
99
+ taxons << taxon_node
100
+ end
101
+
102
+ # @return [String] the string representation of the content item
103
+ def inspect
104
+ if internal_name.nil?
105
+ "LinkedContentItem(title: '#{title}', content_id: '#{content_id}', base_path: '#{base_path}')"
106
+ else
107
+ "LinkedContentItem(title: '#{title}', internal_name: '#{internal_name}', content_id: '#{content_id}', base_path: '#{base_path}')"
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,101 @@
1
+ require 'govuk_taxonomy_helpers/linked_content_item'
2
+
3
+ module GovukTaxonomyHelpers
4
+ class LinkedContentItem
5
+ # Extract a LinkedContentItem from publishing api response data.
6
+ #
7
+ # @param content_item [Hash] Publishing API `get_content` response hash
8
+ # @param expanded_links [Hash] Publishing API `get_expanded_links` response hash
9
+ # @return [LinkedContentItem]
10
+ # @see http://www.rubydoc.info/gems/gds-api-adapters/GdsApi/PublishingApiV2#get_content-instance_method
11
+ # @see http://www.rubydoc.info/gems/gds-api-adapters/GdsApi%2FPublishingApiV2:get_expanded_links
12
+ def self.from_publishing_api(content_item:, expanded_links:)
13
+ PublishingApiResponse.new(
14
+ content_item: content_item,
15
+ expanded_links: expanded_links,
16
+ ).linked_content_item
17
+ end
18
+ end
19
+
20
+ class PublishingApiResponse
21
+ attr_accessor :linked_content_item
22
+
23
+ # @param content_item [Hash] Publishing API `get_content` response hash
24
+ # @param expanded_links [Hash] Publishing API `get_expanded_links` response hash
25
+ def initialize(content_item:, expanded_links:)
26
+ @linked_content_item = LinkedContentItem.new(
27
+ title: content_item["title"],
28
+ internal_name: content_item["details"]["internal_name"],
29
+ content_id: content_item["content_id"],
30
+ base_path: content_item["base_path"]
31
+ )
32
+
33
+ add_expanded_links(expanded_links)
34
+ end
35
+
36
+ private
37
+
38
+ def add_expanded_links(expanded_links_response)
39
+ child_taxons = expanded_links_response["expanded_links"]["child_taxons"]
40
+ parent_taxons = expanded_links_response["expanded_links"]["parent_taxons"]
41
+ taxons = expanded_links_response["expanded_links"]["taxons"]
42
+
43
+ if !child_taxons.nil?
44
+ child_taxons.each do |child|
45
+ linked_content_item << parse_nested_child(child)
46
+ end
47
+ end
48
+
49
+ if !parent_taxons.nil?
50
+ # Assume no taxon has multiple parents
51
+ single_parent = parent_taxons.first
52
+
53
+ parse_nested_parent(single_parent) << linked_content_item
54
+ end
55
+
56
+ if !taxons.nil?
57
+ taxons.each do |taxon|
58
+ taxon_node = parse_nested_parent(taxon)
59
+ linked_content_item.add_taxon(taxon_node)
60
+ end
61
+ end
62
+ end
63
+
64
+ def parse_nested_child(nested_item)
65
+ nested_linked_content_item = LinkedContentItem.new(
66
+ title: nested_item["title"],
67
+ internal_name: nested_item["details"]["internal_name"],
68
+ content_id: nested_item["content_id"],
69
+ base_path: nested_item["base_path"]
70
+ )
71
+
72
+ child_taxons = nested_item["links"]["child_taxons"]
73
+
74
+ if !child_taxons.nil?
75
+ child_taxons.each do |child|
76
+ nested_linked_content_item << parse_nested_child(child)
77
+ end
78
+ end
79
+
80
+ nested_linked_content_item
81
+ end
82
+
83
+ def parse_nested_parent(nested_item)
84
+ nested_linked_content_item = LinkedContentItem.new(
85
+ title: nested_item["title"],
86
+ internal_name: nested_item["details"]["internal_name"],
87
+ content_id: nested_item["content_id"],
88
+ base_path: nested_item["base_path"]
89
+ )
90
+
91
+ parent_taxons = nested_item["links"]["parent_taxons"]
92
+
93
+ if !parent_taxons.nil?
94
+ single_parent = parent_taxons.first
95
+ parse_nested_parent(single_parent) << nested_linked_content_item
96
+ end
97
+
98
+ nested_linked_content_item
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,3 @@
1
+ module GovukTaxonomyHelpers
2
+ VERSION = "0.0.1".freeze
3
+ end
@@ -0,0 +1,158 @@
1
+ require_relative 'spec_helper'
2
+ require 'govuk_taxonomy_helpers/linked_content_item'
3
+
4
+ RSpec.describe GovukTaxonomyHelpers::LinkedContentItem do
5
+ let(:root_node) { GovukTaxonomyHelpers::LinkedContentItem.new(title: "root-id", content_id: "abc", base_path: "/root-id") }
6
+ let(:child_node_1) { GovukTaxonomyHelpers::LinkedContentItem.new(title: "child-1-id", content_id: "abc", base_path: "/child-1-id") }
7
+
8
+ describe "#<<(child_node)" do
9
+ it "makes one node the child of another node" do
10
+ root_node << child_node_1
11
+
12
+ expect(root_node.tree).to include child_node_1
13
+ expect(child_node_1.parent).to eq root_node
14
+ end
15
+ end
16
+
17
+ describe "#tree" do
18
+ context "given a node with a tree of successors" do
19
+ it "returns an array representing a pre-order traversal of the tree" do
20
+ child_node_2 = GovukTaxonomyHelpers::LinkedContentItem.new(title: "child-2-id", content_id: "abc", base_path: "/child-2-id")
21
+ child_node_3 = GovukTaxonomyHelpers::LinkedContentItem.new(title: "child-3-id", content_id: "abc", base_path: "/child-3-id")
22
+
23
+ root_node << child_node_1
24
+ child_node_1 << child_node_3
25
+ child_node_1 << child_node_2
26
+
27
+ expect(root_node.tree.count).to eq 4
28
+ expect(root_node.tree.first).to eq root_node
29
+ expect(root_node.tree.map(&:title)).to eq %w(root-id child-1-id child-3-id child-2-id)
30
+ expect(child_node_1.tree.map(&:title)).to eq %w(child-1-id child-3-id child-2-id)
31
+ end
32
+ end
33
+
34
+ context "given a single node" do
35
+ it "returns an array containing only that node" do
36
+ expect(root_node.tree.map(&:title)).to eq %w(root-id)
37
+ end
38
+ end
39
+ end
40
+
41
+ describe "#root?" do
42
+ before do
43
+ root_node << child_node_1
44
+ end
45
+
46
+ it "returns true when a node is the root" do
47
+ expect(root_node.root?).to be
48
+ end
49
+
50
+ it "returns false when a node is not the root" do
51
+ expect(child_node_1.root?).to_not be
52
+ end
53
+ end
54
+
55
+ describe "#depth" do
56
+ it "returns the depth of the node in its tree" do
57
+ child_node_2 = GovukTaxonomyHelpers::LinkedContentItem.new(title: "child-2-id", content_id: "abc", base_path: "/child-2-id")
58
+ root_node << child_node_1
59
+ child_node_1 << child_node_2
60
+
61
+ expect(root_node.depth).to eq 0
62
+ expect(child_node_1.depth).to eq 1
63
+ expect(child_node_2.depth).to eq 2
64
+ end
65
+ end
66
+
67
+ describe "#count" do
68
+ it "returns the total number of nodes in the tree" do
69
+ root_node << child_node_1
70
+
71
+ expect(root_node.count).to eq 2
72
+ end
73
+ end
74
+
75
+ context "taxon with ancestors" do
76
+ let(:child_node_2) do
77
+ GovukTaxonomyHelpers::LinkedContentItem.new(
78
+ title: "child-2-id",
79
+ content_id: "abc",
80
+ base_path: "/child-2-id"
81
+ )
82
+ end
83
+
84
+ before do
85
+ root_node << child_node_1
86
+ child_node_1 << child_node_2
87
+ end
88
+
89
+ describe "#breadcrumb_trail" do
90
+ it "includes the ancestors plus the content item itself" do
91
+ expect(child_node_2.breadcrumb_trail.map(&:title)).to eq %w(root-id child-1-id child-2-id)
92
+ end
93
+
94
+ it "is just contains itself for the root node" do
95
+ expect(root_node.breadcrumb_trail.map(&:title)).to eq %w(root-id)
96
+ end
97
+ end
98
+
99
+ describe "#ancestors" do
100
+ it "includes the ancestors but not the content item itself" do
101
+ expect(child_node_2.ancestors.map(&:title)).to eq %w(root-id child-1-id)
102
+ end
103
+
104
+ it "is the reverse of #descendants" do
105
+ have_descendant_node = lambda do |ancestor|
106
+ ancestor.descendants.include?(child_node_2)
107
+ end
108
+
109
+ expect(child_node_2.ancestors).to all(satisfy(&have_descendant_node))
110
+ end
111
+
112
+ it "is an empty array for the root node" do
113
+ expect(root_node.ancestors).to be_empty
114
+ end
115
+ end
116
+
117
+ describe "#taxons" do
118
+ let(:content_item) do
119
+ GovukTaxonomyHelpers::LinkedContentItem.new(
120
+ title: "content",
121
+ content_id: "abc",
122
+ base_path: "/content"
123
+ )
124
+ end
125
+
126
+ it "includes only the directly linked taxons" do
127
+ content_item.add_taxon(child_node_2)
128
+
129
+ expect(content_item.taxons.map(&:title)).to eq ["child-2-id"]
130
+ end
131
+ end
132
+
133
+ describe "#taxons_with_ancestors" do
134
+ let(:another_taxon) do
135
+ GovukTaxonomyHelpers::LinkedContentItem.new(
136
+ title: "another-taxon",
137
+ content_id: "abc",
138
+ base_path: "/another-taxon"
139
+ )
140
+ end
141
+
142
+ let(:content_item) do
143
+ GovukTaxonomyHelpers::LinkedContentItem.new(
144
+ title: "content",
145
+ content_id: "abc",
146
+ base_path: "/content"
147
+ )
148
+ end
149
+
150
+ it "includes all of the taxons and all of their anscestors" do
151
+ content_item.add_taxon(child_node_2)
152
+ content_item.add_taxon(another_taxon)
153
+
154
+ expect(content_item.taxons_with_ancestors.map(&:title).sort).to eq %w(another-taxon child-1-id child-2-id root-id)
155
+ end
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,343 @@
1
+ require_relative 'spec_helper'
2
+ require 'govuk_taxonomy_helpers'
3
+
4
+ RSpec.describe GovukTaxonomyHelpers::PublishingApiResponse do
5
+ let(:content_item) do
6
+ {
7
+ "content_id" => "64aadc14-9bca-40d9-abb4-4f21f9792a05",
8
+ "base_path" => "/taxon",
9
+ "title" => "Taxon",
10
+ "details" => {
11
+ "internal_name" => "My lovely taxon"
12
+ }
13
+ }
14
+ end
15
+
16
+ let(:linked_content_item) do
17
+ GovukTaxonomyHelpers::LinkedContentItem.from_publishing_api(
18
+ content_item: content_item,
19
+ expanded_links: expanded_links
20
+ )
21
+ end
22
+
23
+ context "content item with multiple levels of descendants" do
24
+ let(:expanded_links) do
25
+ grandchild_1 = {
26
+ "content_id" => "84aadc14-9bca-40d9-abb4-4f21f9792a05",
27
+ "base_path" => "/grandchild-1",
28
+ "title" => "Grandchild 1",
29
+ "details" => {
30
+ "internal_name" => "GC 1",
31
+ },
32
+ "links" => {}
33
+ }
34
+
35
+ grandchild_2 = {
36
+ "content_id" => "94aadc14-9bca-40d9-abb4-4f21f9792a05",
37
+ "base_path" => "/grandchild-2",
38
+ "title" => "Grandchild 2",
39
+ "details" => {
40
+ "internal_name" => "GC 2",
41
+ },
42
+ "links" => {}
43
+ }
44
+
45
+ child_1 = {
46
+ "content_id" => "74aadc14-9bca-40d9-abb4-4f21f9792a05",
47
+ "base_path" => "/child-1",
48
+ "title" => "Child 1",
49
+ "details" => {
50
+ "internal_name" => "C 1",
51
+ },
52
+ "links" => {
53
+ "child_taxons" => [
54
+ grandchild_1,
55
+ grandchild_2
56
+ ]
57
+ }
58
+ }
59
+
60
+ {
61
+ "content_id" => "64aadc14-9bca-40d9-abb4-4f21f9792a05",
62
+ "expanded_links" => {
63
+ "child_taxons" => [child_1]
64
+ }
65
+ }
66
+ end
67
+
68
+ it "parses titles" do
69
+ expect(linked_content_item.title).to eq("Taxon")
70
+ expect(linked_content_item.children.map(&:title)).to eq(['Child 1'])
71
+ expect(linked_content_item.children.first.children.map(&:title)).to eq(["Grandchild 1", "Grandchild 2"])
72
+ end
73
+
74
+ it "parses internal names" do
75
+ expect(linked_content_item.internal_name).to eq("My lovely taxon")
76
+ expect(linked_content_item.children.map(&:internal_name)).to eq(['C 1'])
77
+ expect(linked_content_item.children.first.children.map(&:internal_name)).to eq(["GC 1", "GC 2"])
78
+ end
79
+ end
80
+
81
+ context "content item with no descendants" do
82
+ let(:expanded_links) do
83
+ {
84
+ "content_id" => "64aadc14-9bca-40d9-abb4-4f21f9792a05",
85
+ "expanded_links" => {}
86
+ }
87
+ end
88
+
89
+ it "parses each level of taxons" do
90
+ expect(linked_content_item.title).to eq("Taxon")
91
+ expect(linked_content_item.children).to be_empty
92
+ end
93
+ end
94
+
95
+ context "content item with children but no grandchildren" do
96
+ let(:expanded_links) do
97
+ child_1 = {
98
+ "content_id" => "74aadc14-9bca-40d9-abb4-4f21f9792a05",
99
+ "base_path" => "/child-1",
100
+ "title" => "Child 1",
101
+ "details" => {
102
+ "internal_name" => "C 1",
103
+ },
104
+ "links" => {}
105
+ }
106
+
107
+ child_2 = {
108
+ "content_id" => "84aadc14-9bca-40d9-abb4-4f21f9792a05",
109
+ "base_path" => "/child-2",
110
+ "title" => "Child 2",
111
+ "details" => {
112
+ "internal_name" => "C 2",
113
+ },
114
+ "links" => {}
115
+ }
116
+
117
+ {
118
+ "content_id" => "64aadc14-9bca-40d9-abb4-4f21f9792a05",
119
+ "expanded_links" => {
120
+ "child_taxons" => [child_1, child_2]
121
+ }
122
+ }
123
+ end
124
+
125
+ it "parses each level of taxons" do
126
+ expect(linked_content_item.title).to eq("Taxon")
127
+ expect(linked_content_item.children.map(&:title)).to eq(["Child 1", "Child 2"])
128
+ expect(linked_content_item.children.map(&:children)).to all(be_empty)
129
+ end
130
+ end
131
+
132
+ context "content item with parents and grandparents" do
133
+ let(:expanded_links) do
134
+ grandparent_1 = {
135
+ "content_id" => "84aadc14-9bca-40d9-abb4-4f21f9792a05",
136
+ "base_path" => "/grandparent-1",
137
+ "title" => "Grandparent 1",
138
+ "details" => {
139
+ "internal_name" => "GP 1",
140
+ },
141
+ "links" => {}
142
+ }
143
+
144
+ parent_1 = {
145
+ "content_id" => "74aadc14-9bca-40d9-abb4-4f21f9792a05",
146
+ "base_path" => "/parent-1",
147
+ "title" => "Parent 1",
148
+ "details" => {
149
+ "internal_name" => "P 1",
150
+ },
151
+ "links" => {
152
+ "parent_taxons" => [
153
+ grandparent_1
154
+ ]
155
+ }
156
+ }
157
+
158
+ {
159
+ "content_id" => "64aadc14-9bca-40d9-abb4-4f21f9792a05",
160
+ "expanded_links" => {
161
+ "parent_taxons" => [parent_1]
162
+ }
163
+ }
164
+ end
165
+
166
+ it "parses the ancestors" do
167
+ expect(linked_content_item.title).to eq("Taxon")
168
+ expect(linked_content_item.parent.title).to eq("Parent 1")
169
+ expect(linked_content_item.ancestors.map(&:title)).to eq(["Grandparent 1", "Parent 1"])
170
+ end
171
+ end
172
+
173
+
174
+ context "content item with parents and no grandparents" do
175
+ let(:expanded_links) do
176
+ parent_1 = {
177
+ "content_id" => "74aadc14-9bca-40d9-abb4-4f21f9792a05",
178
+ "base_path" => "/parent-1",
179
+ "title" => "Parent 1",
180
+ "details" => {
181
+ "internal_name" => "P 1",
182
+ },
183
+ "links" => {}
184
+ }
185
+
186
+ {
187
+ "content_id" => "64aadc14-9bca-40d9-abb4-4f21f9792a05",
188
+ "expanded_links" => {
189
+ "parent_taxons" => [parent_1]
190
+ }
191
+ }
192
+ end
193
+
194
+ it "parses the ancestors" do
195
+ expect(linked_content_item.title).to eq("Taxon")
196
+ expect(linked_content_item.parent.title).to eq("Parent 1")
197
+ expect(linked_content_item.ancestors.map(&:title)).to eq(["Parent 1"])
198
+ end
199
+ end
200
+
201
+ context "content item with no parents" do
202
+ let(:expanded_links) do
203
+ {
204
+ "content_id" => "64aadc14-9bca-40d9-abb4-4f21f9792a05",
205
+ "expanded_links" => {}
206
+ }
207
+ end
208
+
209
+ it "parses the ancestors" do
210
+ expect(linked_content_item.title).to eq("Taxon")
211
+ expect(linked_content_item.parent).to be_nil
212
+ expect(linked_content_item.ancestors.map(&:title)).to be_empty
213
+ end
214
+ end
215
+
216
+ context "content item with multiple parents" do
217
+ let(:expanded_links) do
218
+ parent_1 = {
219
+ "content_id" => "74aadc14-9bca-40d9-abb4-4f21f9792a05",
220
+ "base_path" => "/parent-1",
221
+ "title" => "Parent 1",
222
+ "details" => {
223
+ "internal_name" => "P 1",
224
+ },
225
+ "links" => {}
226
+ }
227
+
228
+ parent_2 = {
229
+ "content_id" => "84aadc14-9bca-40d9-abb4-4f21f9792a05",
230
+ "base_path" => "/parent-2",
231
+ "title" => "Parent 2",
232
+ "details" => {
233
+ "internal_name" => "P 2",
234
+ },
235
+ "links" => {}
236
+ }
237
+
238
+ {
239
+ "content_id" => "64aadc14-9bca-40d9-abb4-4f21f9792a05",
240
+ "expanded_links" => {
241
+ "parent_taxons" => [parent_1, parent_2]
242
+ }
243
+ }
244
+ end
245
+
246
+ it "uses only the first parent" do
247
+ expect(linked_content_item.title).to eq("Taxon")
248
+ expect(linked_content_item.parent.title).to eq("Parent 1")
249
+ expect(linked_content_item.ancestors.map(&:title)).to eq(["Parent 1"])
250
+ end
251
+ end
252
+
253
+ context "a content item tagged to multiple taxons" do
254
+ let(:expanded_links) do
255
+ grandparent_1 = {
256
+ "content_id" => "22aadc14-9bca-40d9-abb4-4f21f9792a05",
257
+ "base_path" => "/grandparent-1",
258
+ "title" => "Grandparent 1",
259
+ "details" => {
260
+ "internal_name" => "GP 1",
261
+ },
262
+ "links" => {}
263
+ }
264
+
265
+ parent_1 = {
266
+ "content_id" => "11aadc14-9bca-40d9-abb4-4f21f9792a05",
267
+ "base_path" => "/parent-1",
268
+ "title" => "Parent 1",
269
+ "details" => {
270
+ "internal_name" => "P 1",
271
+ },
272
+ "links" => {
273
+ "parent_taxons" => [grandparent_1]
274
+ }
275
+ }
276
+
277
+ taxon_1 = {
278
+ "content_id" => "00aadc14-9bca-40d9-abb4-4f21f9792a05",
279
+ "base_path" => "/this-is-a-taxon",
280
+ "title" => "Taxon 1",
281
+ "details" => {
282
+ "internal_name" => "T 1",
283
+ },
284
+ "links" => {
285
+ "parent_taxons" => [parent_1]
286
+ }
287
+ }
288
+
289
+ grandparent_2 = {
290
+ "content_id" => "03aadc14-9bca-40d9-abb4-4f21f9792a05",
291
+ "base_path" => "/grandparent-2",
292
+ "title" => "Grandparent 2",
293
+ "details" => {
294
+ "internal_name" => "GP 2",
295
+ },
296
+ "links" => {}
297
+ }
298
+
299
+ parent_2 = {
300
+ "content_id" => "02aadc14-9bca-40d9-abb4-4f21f9792a05",
301
+ "base_path" => "/parent-2",
302
+ "title" => "Parent 2",
303
+ "details" => {
304
+ "internal_name" => "P 2",
305
+ },
306
+ "links" => {
307
+ "parent_taxons" => [grandparent_2]
308
+ }
309
+ }
310
+
311
+ taxon_2 = {
312
+ "content_id" => "01aadc14-9bca-40d9-abb4-4f21f9792a05",
313
+ "base_path" => "/this-is-also-a-taxon",
314
+ "title" => "Taxon 2",
315
+ "details" => {
316
+ "internal_name" => "T 2",
317
+ },
318
+ "links" => {
319
+ "parent_taxons" => [parent_2]
320
+ }
321
+ }
322
+
323
+ {
324
+ "content_id" => "64aadc14-9bca-40d9-abb4-4f21f9792a05",
325
+ "expanded_links" => {
326
+ "taxons" => [taxon_1, taxon_2]
327
+ }
328
+ }
329
+ end
330
+
331
+ it "parses the taxons and their ancestors" do
332
+ expect(linked_content_item.parent).to be_nil
333
+ expect(linked_content_item.taxons.map(&:title)).to eq(["Taxon 1", "Taxon 2"])
334
+ expect(linked_content_item.taxons_with_ancestors.map(&:title).sort).to eq(
335
+ [
336
+ "Grandparent 1", "Grandparent 2",
337
+ "Parent 1", "Parent 2",
338
+ "Taxon 1", "Taxon 2",
339
+ ]
340
+ )
341
+ end
342
+ end
343
+ end
@@ -0,0 +1,99 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # The generated `.rspec` file contains `--require spec_helper` which will cause
4
+ # this file to always be loaded, without a need to explicitly require it in any
5
+ # files.
6
+ #
7
+ # Given that it is always loaded, you are encouraged to keep this file as
8
+ # light-weight as possible. Requiring heavyweight dependencies from this file
9
+ # will add to the boot time of your test suite on EVERY test run, even for an
10
+ # individual file that may not need all of that loaded. Instead, consider making
11
+ # a separate helper file that requires the additional dependencies and performs
12
+ # the additional setup, and require it from the spec files that actually need
13
+ # it.
14
+ #
15
+ # The `.rspec` file also contains a few flags that are not defaults but that
16
+ # users commonly want.
17
+ #
18
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
19
+
20
+ require 'pry-byebug'
21
+
22
+ RSpec.configure do |config|
23
+ # rspec-expectations config goes here. You can use an alternate
24
+ # assertion/expectation library such as wrong or the stdlib/minitest
25
+ # assertions if you prefer.
26
+ config.expect_with :rspec do |expectations|
27
+ # This option will default to `true` in RSpec 4. It makes the `description`
28
+ # and `failure_message` of custom matchers include text for helper methods
29
+ # defined using `chain`, e.g.:
30
+ # be_bigger_than(2).and_smaller_than(4).description
31
+ # # => "be bigger than 2 and smaller than 4"
32
+ # ...rather than:
33
+ # # => "be bigger than 2"
34
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
35
+ end
36
+
37
+ # rspec-mocks config goes here. You can use an alternate test double
38
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
39
+ config.mock_with :rspec do |mocks|
40
+ # Prevents you from mocking or stubbing a method that does not exist on
41
+ # a real object. This is generally recommended, and will default to
42
+ # `true` in RSpec 4.
43
+ mocks.verify_partial_doubles = true
44
+ end
45
+
46
+ # The settings below are suggested to provide a good initial experience
47
+ # with RSpec, but feel free to customize to your heart's content.
48
+ =begin
49
+ # These two settings work together to allow you to limit a spec run
50
+ # to individual examples or groups you care about by tagging them with
51
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
52
+ # get run.
53
+ config.filter_run :focus
54
+ config.run_all_when_everything_filtered = true
55
+
56
+ # Allows RSpec to persist some state between runs in order to support
57
+ # the `--only-failures` and `--next-failure` CLI options. We recommend
58
+ # you configure your source control system to ignore this file.
59
+ config.example_status_persistence_file_path = "spec/examples.txt"
60
+
61
+ # Limits the available syntax to the non-monkey patched syntax that is
62
+ # recommended. For more details, see:
63
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
64
+ # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
65
+ # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
66
+ config.disable_monkey_patching!
67
+
68
+ # This setting enables warnings. It's recommended, but in some cases may
69
+ # be too noisy due to issues in dependencies.
70
+ config.warnings = true
71
+
72
+ # Many RSpec users commonly either run the entire suite or an individual
73
+ # file, and it's useful to allow more verbose output when running an
74
+ # individual spec file.
75
+ if config.files_to_run.one?
76
+ # Use the documentation formatter for detailed output,
77
+ # unless a formatter has already been configured
78
+ # (e.g. via a command-line flag).
79
+ config.default_formatter = 'doc'
80
+ end
81
+
82
+ # Print the 10 slowest examples and example groups at the
83
+ # end of the spec run, to help surface which specs are running
84
+ # particularly slow.
85
+ config.profile_examples = 10
86
+
87
+ # Run specs in random order to surface order dependencies. If you find an
88
+ # order dependency and want to debug it, you can fix the order by providing
89
+ # the seed, which is printed after each run.
90
+ # --seed 1234
91
+ config.order = :random
92
+
93
+ # Seed global randomization in this process using the `--seed` CLI option.
94
+ # Setting this allows you to use `--seed` to deterministically reproduce
95
+ # test failures related to randomization by passing the same `--seed` value
96
+ # as the one that triggered the failure.
97
+ Kernel.srand config.seed
98
+ =end
99
+ end
metadata ADDED
@@ -0,0 +1,150 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: govuk_taxonomy_helpers
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Government Digital Service
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-03-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.5'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.5'
55
+ - !ruby/object:Gem::Dependency
56
+ name: govuk-lint
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 2.0.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 2.0.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry-byebug
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.4'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.4'
83
+ - !ruby/object:Gem::Dependency
84
+ name: yard
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.9.8
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.9.8
97
+ description:
98
+ email:
99
+ - govuk-dev@digital.cabinet-office.gov.uk
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".gitignore"
105
+ - ".rspec"
106
+ - ".rubocop.yml"
107
+ - ".ruby-version"
108
+ - ".yardopts"
109
+ - CHANGELOG.md
110
+ - Gemfile
111
+ - Jenkinsfile
112
+ - LICENCE.txt
113
+ - README.md
114
+ - Rakefile
115
+ - govuk_taxonomy_helpers.gemspec
116
+ - lib/govuk_taxonomy_helpers.rb
117
+ - lib/govuk_taxonomy_helpers/linked_content_item.rb
118
+ - lib/govuk_taxonomy_helpers/publishing_api_response.rb
119
+ - lib/govuk_taxonomy_helpers/version.rb
120
+ - spec/linked_content_item_spec.rb
121
+ - spec/publishing_api_response_spec.rb
122
+ - spec/spec_helper.rb
123
+ homepage: https://github.com/alphagov/govuk_taxonomy_helpers
124
+ licenses:
125
+ - MIT
126
+ metadata: {}
127
+ post_install_message:
128
+ rdoc_options: []
129
+ require_paths:
130
+ - lib
131
+ required_ruby_version: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ required_rubygems_version: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ version: '0'
141
+ requirements: []
142
+ rubyforge_project:
143
+ rubygems_version: 2.6.8
144
+ signing_key:
145
+ specification_version: 4
146
+ summary: Parses the taxonomy of GOV.UK into a browseable tree structure.
147
+ test_files:
148
+ - spec/linked_content_item_spec.rb
149
+ - spec/publishing_api_response_spec.rb
150
+ - spec/spec_helper.rb