github.com/opentofu/opentofu@v1.7.1/internal/command/jsonformat/computed/renderers/renderer_test.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  package renderers
     7  
     8  import (
     9  	"encoding/json"
    10  	"strings"
    11  	"testing"
    12  
    13  	"github.com/opentofu/opentofu/internal/command/jsonformat/computed"
    14  
    15  	"github.com/google/go-cmp/cmp"
    16  	"github.com/mitchellh/colorstring"
    17  	"github.com/zclconf/go-cty/cty"
    18  
    19  	"github.com/opentofu/opentofu/internal/plans"
    20  )
    21  
    22  func TestRenderers_Human(t *testing.T) {
    23  	colorize := colorstring.Colorize{
    24  		Colors:  colorstring.DefaultColors,
    25  		Disable: true,
    26  	}
    27  
    28  	tcs := map[string]struct {
    29  		diff     computed.Diff
    30  		expected string
    31  		opts     computed.RenderHumanOpts
    32  	}{
    33  		// We're using the string "null" in these tests to demonstrate the
    34  		// difference between rendering an actual string and rendering a null
    35  		// value.
    36  		"primitive_create_string": {
    37  			diff: computed.Diff{
    38  				Renderer: Primitive(nil, "null", cty.String),
    39  				Action:   plans.Create,
    40  			},
    41  			expected: "\"null\"",
    42  		},
    43  		"primitive_delete_string": {
    44  			diff: computed.Diff{
    45  				Renderer: Primitive("null", nil, cty.String),
    46  				Action:   plans.Delete,
    47  			},
    48  			expected: "\"null\" -> null",
    49  		},
    50  		"primitive_update_string_to_null": {
    51  			diff: computed.Diff{
    52  				Renderer: Primitive("null", nil, cty.String),
    53  				Action:   plans.Update,
    54  			},
    55  			expected: "\"null\" -> null",
    56  		},
    57  		"primitive_update_string_from_null": {
    58  			diff: computed.Diff{
    59  				Renderer: Primitive(nil, "null", cty.String),
    60  				Action:   plans.Update,
    61  			},
    62  			expected: "null -> \"null\"",
    63  		},
    64  		"primitive_update_multiline_string_to_null": {
    65  			diff: computed.Diff{
    66  				Renderer: Primitive("nu\nll", nil, cty.String),
    67  				Action:   plans.Update,
    68  			},
    69  			expected: `
    70  <<-EOT
    71        - nu
    72        - ll
    73        + null
    74      EOT
    75  `,
    76  		},
    77  		"primitive_update_multiline_string_from_null": {
    78  			diff: computed.Diff{
    79  				Renderer: Primitive(nil, "nu\nll", cty.String),
    80  				Action:   plans.Update,
    81  			},
    82  			expected: `
    83  <<-EOT
    84        - null
    85        + nu
    86        + ll
    87      EOT
    88  `,
    89  		},
    90  		"primitive_update_json_string_to_null": {
    91  			diff: computed.Diff{
    92  				Renderer: Primitive("[null]", nil, cty.String),
    93  				Action:   plans.Update,
    94  			},
    95  			expected: `
    96  jsonencode(
    97          [
    98            - null,
    99          ]
   100      ) -> null
   101  `,
   102  		},
   103  		"primitive_update_json_string_from_null": {
   104  			diff: computed.Diff{
   105  				Renderer: Primitive(nil, "[null]", cty.String),
   106  				Action:   plans.Update,
   107  			},
   108  			expected: `
   109  null -> jsonencode(
   110          [
   111            + null,
   112          ]
   113      )
   114  `,
   115  		},
   116  		"primitive_create_null_string": {
   117  			diff: computed.Diff{
   118  				Renderer: Primitive(nil, nil, cty.String),
   119  				Action:   plans.Create,
   120  			},
   121  			expected: "null",
   122  		},
   123  		"primitive_delete_null_string": {
   124  			diff: computed.Diff{
   125  				Renderer: Primitive(nil, nil, cty.String),
   126  				Action:   plans.Delete,
   127  			},
   128  			expected: "null",
   129  		},
   130  		"primitive_create": {
   131  			diff: computed.Diff{
   132  				Renderer: Primitive(nil, json.Number("1"), cty.Number),
   133  				Action:   plans.Create,
   134  			},
   135  			expected: "1",
   136  		},
   137  		"primitive_delete": {
   138  			diff: computed.Diff{
   139  				Renderer: Primitive(json.Number("1"), nil, cty.Number),
   140  				Action:   plans.Delete,
   141  			},
   142  			expected: "1 -> null",
   143  		},
   144  		"primitive_delete_override": {
   145  			diff: computed.Diff{
   146  				Renderer: Primitive(json.Number("1"), nil, cty.Number),
   147  				Action:   plans.Delete,
   148  			},
   149  			opts:     computed.RenderHumanOpts{OverrideNullSuffix: true},
   150  			expected: "1",
   151  		},
   152  		"primitive_update_to_null": {
   153  			diff: computed.Diff{
   154  				Renderer: Primitive(json.Number("1"), nil, cty.Number),
   155  				Action:   plans.Update,
   156  			},
   157  			expected: "1 -> null",
   158  		},
   159  		"primitive_update_from_null": {
   160  			diff: computed.Diff{
   161  				Renderer: Primitive(nil, json.Number("1"), cty.Number),
   162  				Action:   plans.Update,
   163  			},
   164  			expected: "null -> 1",
   165  		},
   166  		"primitive_update": {
   167  			diff: computed.Diff{
   168  				Renderer: Primitive(json.Number("0"), json.Number("1"), cty.Number),
   169  				Action:   plans.Update,
   170  			},
   171  			expected: "0 -> 1",
   172  		},
   173  		"primitive_update_max_int64_to_min_int64": {
   174  			diff: computed.Diff{
   175  				// 9223372036854775807 == math.MinInt64
   176  				// -9223372036854775808 == math.MaxInt64
   177  				Renderer: Primitive(json.Number("9223372036854775807"), json.Number("-9223372036854775808"), cty.Number),
   178  				Action:   plans.Update,
   179  			},
   180  			expected: "9223372036854775807 -> -9223372036854775808",
   181  		},
   182  		"primitive_update_21_digits_number": {
   183  			diff: computed.Diff{
   184  				// 18446744073709551615 == math.MaxUint64 (which has 20-digits number)
   185  				Renderer: Primitive(json.Number("918446744073709551615"), json.Number("0"), cty.Number),
   186  				Action:   plans.Update,
   187  			},
   188  			expected: "918446744073709551615 -> 0",
   189  		},
   190  		"primitive_update_replace": {
   191  			diff: computed.Diff{
   192  				Renderer: Primitive(json.Number("0"), json.Number("1"), cty.Number),
   193  				Action:   plans.Update,
   194  				Replace:  true,
   195  			},
   196  			expected: "0 -> 1 # forces replacement",
   197  		},
   198  		"primitive_multiline_string_create": {
   199  			diff: computed.Diff{
   200  				Renderer: Primitive(nil, "hello\nworld", cty.String),
   201  				Action:   plans.Create,
   202  			},
   203  			expected: `
   204  <<-EOT
   205          hello
   206          world
   207      EOT
   208  `,
   209  		},
   210  		"primitive_multiline_string_delete": {
   211  			diff: computed.Diff{
   212  				Renderer: Primitive("hello\nworld", nil, cty.String),
   213  				Action:   plans.Delete,
   214  			},
   215  			expected: `
   216  <<-EOT
   217          hello
   218          world
   219      EOT -> null
   220  `,
   221  		},
   222  		"primitive_multiline_string_update": {
   223  			diff: computed.Diff{
   224  				Renderer: Primitive("hello\nold\nworld", "hello\nnew\nworld", cty.String),
   225  				Action:   plans.Update,
   226  			},
   227  			expected: `
   228  <<-EOT
   229          hello
   230        - old
   231        + new
   232          world
   233      EOT
   234  `,
   235  		},
   236  		"primitive_json_string_create": {
   237  			diff: computed.Diff{
   238  				Renderer: Primitive(nil, "{\"key_one\": \"value_one\",\"key_two\":\"value_two\"}", cty.String),
   239  				Action:   plans.Create,
   240  			},
   241  			expected: `
   242  jsonencode(
   243          {
   244            + key_one = "value_one"
   245            + key_two = "value_two"
   246          }
   247      )
   248  `,
   249  		},
   250  		"primitive_json_string_delete": {
   251  			diff: computed.Diff{
   252  				Renderer: Primitive("{\"key_one\": \"value_one\",\"key_two\":\"value_two\"}", nil, cty.String),
   253  				Action:   plans.Delete,
   254  			},
   255  			expected: `
   256  jsonencode(
   257          {
   258            - key_one = "value_one"
   259            - key_two = "value_two"
   260          }
   261      ) -> null
   262  `,
   263  		},
   264  		"primitive_json_string_update": {
   265  			diff: computed.Diff{
   266  				Renderer: Primitive("{\"key_one\": \"value_one\",\"key_two\":\"value_two\"}", "{\"key_one\": \"value_one\",\"key_two\":\"value_two\",\"key_three\":\"value_three\"}", cty.String),
   267  				Action:   plans.Update,
   268  			},
   269  			expected: `
   270  jsonencode(
   271        ~ {
   272            + key_three = "value_three"
   273              # (2 unchanged attributes hidden)
   274          }
   275      )
   276  `,
   277  		},
   278  		"primitive_json_explicit_nulls": {
   279  			diff: computed.Diff{
   280  				Renderer: Primitive("{\"key_one\":\"value_one\",\"key_two\":\"value_two\"}", "{\"key_one\":null}", cty.String),
   281  				Action:   plans.Update,
   282  			},
   283  			expected: `
   284  jsonencode(
   285        ~ {
   286            ~ key_one = "value_one" -> null
   287            - key_two = "value_two"
   288          }
   289      )
   290  `,
   291  		},
   292  		"primitive_fake_json_string_update": {
   293  			diff: computed.Diff{
   294  				// This isn't valid JSON, our renderer should be okay with it.
   295  				Renderer: Primitive("{\"key_one\": \"value_one\",\"key_two\":\"value_two\"", "{\"key_one\": \"value_one\",\"key_two\":\"value_two\",\"key_three\":\"value_three\"", cty.String),
   296  				Action:   plans.Update,
   297  			},
   298  			expected: "\"{\\\"key_one\\\": \\\"value_one\\\",\\\"key_two\\\":\\\"value_two\\\"\" -> \"{\\\"key_one\\\": \\\"value_one\\\",\\\"key_two\\\":\\\"value_two\\\",\\\"key_three\\\":\\\"value_three\\\"\"",
   299  		},
   300  		"primitive_multiline_to_json_update": {
   301  			diff: computed.Diff{
   302  				Renderer: Primitive("hello\nworld", "{\"key_one\": \"value_one\",\"key_two\":\"value_two\"}", cty.String),
   303  				Action:   plans.Update,
   304  			},
   305  			expected: `
   306  <<-EOT
   307          hello
   308          world
   309      EOT -> jsonencode(
   310          {
   311            + key_one = "value_one"
   312            + key_two = "value_two"
   313          }
   314      )
   315  `,
   316  		},
   317  		"primitive_json_to_multiline_update": {
   318  			diff: computed.Diff{
   319  				Renderer: Primitive("{\"key_one\": \"value_one\",\"key_two\":\"value_two\"}", "hello\nworld", cty.String),
   320  				Action:   plans.Update,
   321  			},
   322  			expected: `
   323  jsonencode(
   324          {
   325            - key_one = "value_one"
   326            - key_two = "value_two"
   327          }
   328      ) -> <<-EOT
   329          hello
   330          world
   331      EOT
   332  `,
   333  		},
   334  		"primitive_json_to_string_update": {
   335  			diff: computed.Diff{
   336  				Renderer: Primitive("{\"key_one\": \"value_one\",\"key_two\":\"value_two\"}", "hello world", cty.String),
   337  				Action:   plans.Update,
   338  			},
   339  			expected: `
   340  jsonencode(
   341          {
   342            - key_one = "value_one"
   343            - key_two = "value_two"
   344          }
   345      ) -> "hello world"
   346  `,
   347  		},
   348  		"primitive_string_to_json_update": {
   349  			diff: computed.Diff{
   350  				Renderer: Primitive("hello world", "{\"key_one\": \"value_one\",\"key_two\":\"value_two\"}", cty.String),
   351  				Action:   plans.Update,
   352  			},
   353  			expected: `
   354  "hello world" -> jsonencode(
   355          {
   356            + key_one = "value_one"
   357            + key_two = "value_two"
   358          }
   359      )
   360  `,
   361  		},
   362  		"primitive_multi_to_single_update": {
   363  			diff: computed.Diff{
   364  				Renderer: Primitive("hello\nworld", "hello world", cty.String),
   365  				Action:   plans.Update,
   366  			},
   367  			expected: `
   368  <<-EOT
   369        - hello
   370        - world
   371        + hello world
   372      EOT
   373  `,
   374  		},
   375  		"primitive_single_to_multi_update": {
   376  			diff: computed.Diff{
   377  				Renderer: Primitive("hello world", "hello\nworld", cty.String),
   378  				Action:   plans.Update,
   379  			},
   380  			expected: `
   381  <<-EOT
   382        - hello world
   383        + hello
   384        + world
   385      EOT
   386  `,
   387  		},
   388  		"sensitive_update": {
   389  			diff: computed.Diff{
   390  				Renderer: Sensitive(computed.Diff{
   391  					Renderer: Primitive(json.Number("0"), json.Number("1"), cty.Number),
   392  					Action:   plans.Update,
   393  				}, true, true),
   394  				Action: plans.Update,
   395  			},
   396  			expected: "(sensitive value)",
   397  		},
   398  		"sensitive_update_replace": {
   399  			diff: computed.Diff{
   400  				Renderer: Sensitive(computed.Diff{
   401  					Renderer: Primitive(json.Number("0"), json.Number("1"), cty.Number),
   402  					Action:   plans.Update,
   403  					Replace:  true,
   404  				}, true, true),
   405  				Action:  plans.Update,
   406  				Replace: true,
   407  			},
   408  			expected: "(sensitive value) # forces replacement",
   409  		},
   410  		"computed_create": {
   411  			diff: computed.Diff{
   412  				Renderer: Unknown(computed.Diff{}),
   413  				Action:   plans.Create,
   414  			},
   415  			expected: "(known after apply)",
   416  		},
   417  		"computed_update": {
   418  			diff: computed.Diff{
   419  				Renderer: Unknown(computed.Diff{
   420  					Renderer: Primitive(json.Number("0"), nil, cty.Number),
   421  					Action:   plans.Delete,
   422  				}),
   423  				Action: plans.Update,
   424  			},
   425  			expected: "0 -> (known after apply)",
   426  		},
   427  		"computed_create_forces_replacement": {
   428  			diff: computed.Diff{
   429  				Renderer: Unknown(computed.Diff{}),
   430  				Action:   plans.Create,
   431  				Replace:  true,
   432  			},
   433  			expected: "(known after apply) # forces replacement",
   434  		},
   435  		"computed_update_forces_replacement": {
   436  			diff: computed.Diff{
   437  				Renderer: Unknown(computed.Diff{
   438  					Renderer: Primitive(json.Number("0"), nil, cty.Number),
   439  					Action:   plans.Delete,
   440  				}),
   441  				Action:  plans.Update,
   442  				Replace: true,
   443  			},
   444  			expected: "0 -> (known after apply) # forces replacement",
   445  		},
   446  		"object_created": {
   447  			diff: computed.Diff{
   448  				Renderer: Object(map[string]computed.Diff{}),
   449  				Action:   plans.Create,
   450  			},
   451  			expected: "{}",
   452  		},
   453  		"object_created_with_attributes": {
   454  			diff: computed.Diff{
   455  				Renderer: Object(map[string]computed.Diff{
   456  					"attribute_one": {
   457  						Renderer: Primitive(nil, json.Number("0"), cty.Number),
   458  						Action:   plans.Create,
   459  					},
   460  				}),
   461  				Action: plans.Create,
   462  			},
   463  			expected: `
   464  {
   465        + attribute_one = 0
   466      }
   467  `,
   468  		},
   469  		"object_deleted": {
   470  			diff: computed.Diff{
   471  				Renderer: Object(map[string]computed.Diff{}),
   472  				Action:   plans.Delete,
   473  			},
   474  			expected: "{} -> null",
   475  		},
   476  		"object_deleted_with_attributes": {
   477  			diff: computed.Diff{
   478  				Renderer: Object(map[string]computed.Diff{
   479  					"attribute_one": {
   480  						Renderer: Primitive(json.Number("0"), nil, cty.Number),
   481  						Action:   plans.Delete,
   482  					},
   483  				}),
   484  				Action: plans.Delete,
   485  			},
   486  			expected: `
   487  {
   488        - attribute_one = 0
   489      } -> null
   490  `,
   491  		},
   492  		"nested_object_deleted": {
   493  			diff: computed.Diff{
   494  				Renderer: NestedObject(map[string]computed.Diff{}),
   495  				Action:   plans.Delete,
   496  			},
   497  			expected: "{} -> null",
   498  		},
   499  		"nested_object_deleted_with_attributes": {
   500  			diff: computed.Diff{
   501  				Renderer: NestedObject(map[string]computed.Diff{
   502  					"attribute_one": {
   503  						Renderer: Primitive(json.Number("0"), nil, cty.Number),
   504  						Action:   plans.Delete,
   505  					},
   506  				}),
   507  				Action: plans.Delete,
   508  			},
   509  			expected: `
   510  {
   511        - attribute_one = 0 -> null
   512      } -> null
   513  `,
   514  		},
   515  		"object_create_attribute": {
   516  			diff: computed.Diff{
   517  				Renderer: Object(map[string]computed.Diff{
   518  					"attribute_one": {
   519  						Renderer: Primitive(nil, json.Number("0"), cty.Number),
   520  						Action:   plans.Create,
   521  					},
   522  				}),
   523  				Action: plans.Update,
   524  			},
   525  			expected: `
   526  {
   527        + attribute_one = 0
   528      }
   529  `,
   530  		},
   531  		"object_update_attribute": {
   532  			diff: computed.Diff{
   533  				Renderer: Object(map[string]computed.Diff{
   534  					"attribute_one": {
   535  						Renderer: Primitive(json.Number("0"), json.Number("1"), cty.Number),
   536  						Action:   plans.Update,
   537  					},
   538  				}),
   539  				Action: plans.Update,
   540  			},
   541  			expected: `
   542  {
   543        ~ attribute_one = 0 -> 1
   544      }
   545  `,
   546  		},
   547  		"object_update_attribute_forces_replacement": {
   548  			diff: computed.Diff{
   549  				Renderer: Object(map[string]computed.Diff{
   550  					"attribute_one": {
   551  						Renderer: Primitive(json.Number("0"), json.Number("1"), cty.Number),
   552  						Action:   plans.Update,
   553  					},
   554  				}),
   555  				Action:  plans.Update,
   556  				Replace: true,
   557  			},
   558  			expected: `
   559  { # forces replacement
   560        ~ attribute_one = 0 -> 1
   561      }
   562  `,
   563  		},
   564  		"object_delete_attribute": {
   565  			diff: computed.Diff{
   566  				Renderer: Object(map[string]computed.Diff{
   567  					"attribute_one": {
   568  						Renderer: Primitive(json.Number("0"), nil, cty.Number),
   569  						Action:   plans.Delete,
   570  					},
   571  				}),
   572  				Action: plans.Update,
   573  			},
   574  			expected: `
   575  {
   576        - attribute_one = 0
   577      }
   578  `,
   579  		},
   580  		"object_ignore_unchanged_attributes": {
   581  			diff: computed.Diff{
   582  				Renderer: Object(map[string]computed.Diff{
   583  					"attribute_one": {
   584  						Renderer: Primitive(json.Number("0"), json.Number("1"), cty.Number),
   585  						Action:   plans.Update,
   586  					},
   587  					"attribute_two": {
   588  						Renderer: Primitive(json.Number("0"), json.Number("0"), cty.Number),
   589  						Action:   plans.NoOp,
   590  					},
   591  					"attribute_three": {
   592  						Renderer: Primitive(nil, json.Number("1"), cty.Number),
   593  						Action:   plans.Create,
   594  					},
   595  				}),
   596  				Action: plans.Update,
   597  			},
   598  			expected: `
   599  {
   600        ~ attribute_one   = 0 -> 1
   601        + attribute_three = 1
   602          # (1 unchanged attribute hidden)
   603      }
   604  `,
   605  		},
   606  		"object_create_sensitive_attribute": {
   607  			diff: computed.Diff{
   608  				Renderer: Object(map[string]computed.Diff{
   609  					"attribute_one": {
   610  						Renderer: Sensitive(computed.Diff{
   611  							Renderer: Primitive(nil, json.Number("1"), cty.Number),
   612  							Action:   plans.Create,
   613  						}, false, true),
   614  						Action: plans.Create,
   615  					},
   616  				}),
   617  				Action: plans.Update,
   618  			},
   619  			expected: `
   620  {
   621        + attribute_one = (sensitive value)
   622      }
   623  `,
   624  		},
   625  		"object_update_sensitive_attribute": {
   626  			diff: computed.Diff{
   627  				Renderer: Object(map[string]computed.Diff{
   628  					"attribute_one": {
   629  						Renderer: Sensitive(computed.Diff{
   630  							Renderer: Primitive(json.Number("0"), json.Number("1"), cty.Number),
   631  							Action:   plans.Update,
   632  						}, true, true),
   633  						Action: plans.Update,
   634  					},
   635  				}),
   636  				Action: plans.Update,
   637  			},
   638  			expected: `
   639  {
   640        ~ attribute_one = (sensitive value)
   641      }
   642  `,
   643  		},
   644  		"object_delete_sensitive_attribute": {
   645  			diff: computed.Diff{
   646  				Renderer: Object(map[string]computed.Diff{
   647  					"attribute_one": {
   648  						Renderer: Sensitive(computed.Diff{
   649  							Renderer: Primitive(json.Number("0"), nil, cty.Number),
   650  							Action:   plans.Delete,
   651  						}, true, false),
   652  						Action: plans.Delete,
   653  					},
   654  				}),
   655  				Action: plans.Update,
   656  			},
   657  			expected: `
   658  {
   659        - attribute_one = (sensitive value)
   660      }
   661  `,
   662  		},
   663  		"object_create_computed_attribute": {
   664  			diff: computed.Diff{
   665  				Renderer: Object(map[string]computed.Diff{
   666  					"attribute_one": {
   667  						Renderer: Unknown(computed.Diff{Renderer: nil}),
   668  						Action:   plans.Create,
   669  					},
   670  				}),
   671  				Action: plans.Update,
   672  			},
   673  			expected: `
   674  {
   675        + attribute_one = (known after apply)
   676      }
   677  `,
   678  		},
   679  		"object_update_computed_attribute": {
   680  			diff: computed.Diff{
   681  				Renderer: Object(map[string]computed.Diff{
   682  					"attribute_one": {
   683  						Renderer: Unknown(computed.Diff{
   684  							Renderer: Primitive(json.Number("1"), nil, cty.Number),
   685  							Action:   plans.Delete,
   686  						}),
   687  						Action: plans.Update,
   688  					},
   689  				}),
   690  				Action: plans.Update,
   691  			},
   692  			expected: `
   693  {
   694        ~ attribute_one = 1 -> (known after apply)
   695      }
   696  `,
   697  		},
   698  		"object_escapes_attribute_keys": {
   699  			diff: computed.Diff{
   700  				Renderer: Object(map[string]computed.Diff{
   701  					"attribute_one": {
   702  						Renderer: Primitive(json.Number("1"), json.Number("2"), cty.Number),
   703  						Action:   plans.Update,
   704  					},
   705  					"attribute:two": {
   706  						Renderer: Primitive(json.Number("2"), json.Number("3"), cty.Number),
   707  						Action:   plans.Update,
   708  					},
   709  					"attribute_six": {
   710  						Renderer: Primitive(json.Number("3"), json.Number("4"), cty.Number),
   711  						Action:   plans.Update,
   712  					},
   713  				}),
   714  				Action: plans.Update,
   715  			},
   716  			expected: `
   717  {
   718        ~ "attribute:two" = 2 -> 3
   719        ~ attribute_one   = 1 -> 2
   720        ~ attribute_six   = 3 -> 4
   721      }
   722  `,
   723  		},
   724  		"map_create_empty": {
   725  			diff: computed.Diff{
   726  				Renderer: Map(map[string]computed.Diff{}),
   727  				Action:   plans.Create,
   728  			},
   729  			expected: "{}",
   730  		},
   731  		"map_create": {
   732  			diff: computed.Diff{
   733  				Renderer: Map(map[string]computed.Diff{
   734  					"element_one": {
   735  						Renderer: Primitive(nil, "new", cty.String),
   736  						Action:   plans.Create,
   737  					},
   738  				}),
   739  				Action: plans.Create,
   740  			},
   741  			expected: `
   742  {
   743        + "element_one" = "new"
   744      }
   745  `,
   746  		},
   747  		"map_delete_empty": {
   748  			diff: computed.Diff{
   749  				Renderer: Map(map[string]computed.Diff{}),
   750  				Action:   plans.Delete,
   751  			},
   752  			expected: "{} -> null",
   753  		},
   754  		"map_delete": {
   755  			diff: computed.Diff{
   756  				Renderer: Map(map[string]computed.Diff{
   757  					"element_one": {
   758  						Renderer: Primitive("old", nil, cty.String),
   759  						Action:   plans.Delete,
   760  					},
   761  				}),
   762  				Action: plans.Delete,
   763  			},
   764  			expected: `
   765  {
   766        - "element_one" = "old"
   767      } -> null
   768  `,
   769  		},
   770  		"map_create_element": {
   771  			diff: computed.Diff{
   772  				Renderer: Map(map[string]computed.Diff{
   773  					"element_one": {
   774  						Renderer: Primitive(nil, "new", cty.String),
   775  						Action:   plans.Create,
   776  					},
   777  				}),
   778  				Action: plans.Update,
   779  			},
   780  			expected: `
   781  {
   782        + "element_one" = "new"
   783      }
   784  `,
   785  		},
   786  		"map_update_element": {
   787  			diff: computed.Diff{
   788  				Renderer: Map(map[string]computed.Diff{
   789  					"element_one": {
   790  						Renderer: Primitive("old", "new", cty.String),
   791  						Action:   plans.Update,
   792  					},
   793  				}),
   794  				Action: plans.Update,
   795  			},
   796  			expected: `
   797  {
   798        ~ "element_one" = "old" -> "new"
   799      }
   800  `,
   801  		},
   802  		"map_delete_element": {
   803  			diff: computed.Diff{
   804  				Renderer: Map(map[string]computed.Diff{
   805  					"element_one": {
   806  						Renderer: Primitive("old", nil, cty.String),
   807  						Action:   plans.Delete,
   808  					},
   809  				}),
   810  				Action: plans.Update,
   811  			},
   812  			expected: `
   813  {
   814        - "element_one" = "old" -> null
   815      }
   816  `,
   817  		},
   818  		"map_update_forces_replacement": {
   819  			diff: computed.Diff{
   820  				Renderer: Map(map[string]computed.Diff{
   821  					"element_one": {
   822  						Renderer: Primitive("old", "new", cty.String),
   823  						Action:   plans.Update,
   824  					},
   825  				}),
   826  				Action:  plans.Update,
   827  				Replace: true,
   828  			},
   829  			expected: `
   830  { # forces replacement
   831        ~ "element_one" = "old" -> "new"
   832      }
   833  `,
   834  		},
   835  		"map_ignore_unchanged_elements": {
   836  			diff: computed.Diff{
   837  				Renderer: Map(map[string]computed.Diff{
   838  					"element_one": {
   839  						Renderer: Primitive(nil, "new", cty.String),
   840  						Action:   plans.Create,
   841  					},
   842  					"element_two": {
   843  						Renderer: Primitive("old", "old", cty.String),
   844  						Action:   plans.NoOp,
   845  					},
   846  					"element_three": {
   847  						Renderer: Primitive("old", "new", cty.String),
   848  						Action:   plans.Update,
   849  					},
   850  				}),
   851  				Action: plans.Update,
   852  			},
   853  			expected: `
   854  {
   855        + "element_one"   = "new"
   856        ~ "element_three" = "old" -> "new"
   857          # (1 unchanged element hidden)
   858      }
   859  `,
   860  		},
   861  		"map_create_sensitive_element": {
   862  			diff: computed.Diff{
   863  				Renderer: Map(map[string]computed.Diff{
   864  					"element_one": {
   865  						Renderer: Sensitive(computed.Diff{
   866  							Renderer: Primitive(nil, json.Number("1"), cty.Number),
   867  							Action:   plans.Create,
   868  						}, false, true),
   869  						Action: plans.Create,
   870  					},
   871  				}),
   872  				Action: plans.Update,
   873  			},
   874  			expected: `
   875  {
   876        + "element_one" = (sensitive value)
   877      }
   878  `,
   879  		},
   880  		"map_update_sensitive_element": {
   881  			diff: computed.Diff{
   882  				Renderer: Map(map[string]computed.Diff{
   883  					"element_one": {
   884  						Renderer: Sensitive(computed.Diff{
   885  							Renderer: Primitive(json.Number("0"), json.Number("1"), cty.Number),
   886  							Action:   plans.Update,
   887  						}, true, true),
   888  						Action: plans.Update,
   889  					},
   890  				}),
   891  				Action: plans.Update,
   892  			},
   893  			expected: `
   894  {
   895        ~ "element_one" = (sensitive value)
   896      }
   897  `,
   898  		},
   899  		"map_update_sensitive_element_status": {
   900  			diff: computed.Diff{
   901  				Renderer: Map(map[string]computed.Diff{
   902  					"element_one": {
   903  						Renderer: Sensitive(computed.Diff{
   904  							Renderer: Primitive(json.Number("0"), json.Number("0"), cty.Number),
   905  							Action:   plans.NoOp,
   906  						}, true, false),
   907  						Action: plans.Update,
   908  					},
   909  				}),
   910  				Action: plans.Update,
   911  			},
   912  			expected: `
   913  {
   914        # Warning: this attribute value will no longer be marked as sensitive
   915        # after applying this change. The value is unchanged.
   916        ~ "element_one" = (sensitive value)
   917      }
   918  `,
   919  		},
   920  		"map_delete_sensitive_element": {
   921  			diff: computed.Diff{
   922  				Renderer: Map(map[string]computed.Diff{
   923  					"element_one": {
   924  						Renderer: Sensitive(computed.Diff{
   925  							Renderer: Primitive(json.Number("0"), nil, cty.Number),
   926  							Action:   plans.Delete,
   927  						}, true, false),
   928  						Action: plans.Delete,
   929  					},
   930  				}),
   931  				Action: plans.Update,
   932  			},
   933  			expected: `
   934  {
   935        - "element_one" = (sensitive value) -> null
   936      }
   937  `,
   938  		},
   939  		"map_create_computed_element": {
   940  			diff: computed.Diff{
   941  				Renderer: Map(map[string]computed.Diff{
   942  					"element_one": {
   943  						Renderer: Unknown(computed.Diff{}),
   944  						Action:   plans.Create,
   945  					},
   946  				}),
   947  				Action: plans.Update,
   948  			},
   949  			expected: `
   950  {
   951        + "element_one" = (known after apply)
   952      }
   953  `,
   954  		},
   955  		"map_update_computed_element": {
   956  			diff: computed.Diff{
   957  				Renderer: Map(map[string]computed.Diff{
   958  					"element_one": {
   959  						Renderer: Unknown(computed.Diff{
   960  							Renderer: Primitive(json.Number("1"), nil, cty.Number),
   961  							Action:   plans.Delete,
   962  						}),
   963  						Action: plans.Update,
   964  					},
   965  				}),
   966  				Action: plans.Update,
   967  			},
   968  			expected: `
   969  {
   970        ~ "element_one" = 1 -> (known after apply)
   971      }
   972  `,
   973  		},
   974  		"map_aligns_key": {
   975  			diff: computed.Diff{
   976  				Renderer: Map(map[string]computed.Diff{
   977  					"element_one": {
   978  						Renderer: Primitive(json.Number("1"), json.Number("2"), cty.Number),
   979  						Action:   plans.Update,
   980  					},
   981  					"element_two": {
   982  						Renderer: Primitive(json.Number("1"), json.Number("3"), cty.Number),
   983  						Action:   plans.Update,
   984  					},
   985  					"element_three": {
   986  						Renderer: Primitive(json.Number("1"), json.Number("4"), cty.Number),
   987  						Action:   plans.Update,
   988  					},
   989  				}),
   990  				Action: plans.Update,
   991  			},
   992  			expected: `
   993  {
   994        ~ "element_one"   = 1 -> 2
   995        ~ "element_three" = 1 -> 4
   996        ~ "element_two"   = 1 -> 3
   997      }
   998  `,
   999  		},
  1000  		"nested_map_does_not_align_keys": {
  1001  			diff: computed.Diff{
  1002  				Renderer: NestedMap(map[string]computed.Diff{
  1003  					"element_one": {
  1004  						Renderer: Primitive(json.Number("1"), json.Number("2"), cty.Number),
  1005  						Action:   plans.Update,
  1006  					},
  1007  					"element_two": {
  1008  						Renderer: Primitive(json.Number("1"), json.Number("3"), cty.Number),
  1009  						Action:   plans.Update,
  1010  					},
  1011  					"element_three": {
  1012  						Renderer: Primitive(json.Number("1"), json.Number("4"), cty.Number),
  1013  						Action:   plans.Update,
  1014  					},
  1015  				}),
  1016  				Action: plans.Update,
  1017  			},
  1018  			expected: `
  1019  {
  1020        ~ "element_one" = 1 -> 2
  1021        ~ "element_three" = 1 -> 4
  1022        ~ "element_two" = 1 -> 3
  1023      }
  1024  `,
  1025  		},
  1026  		"list_create_empty": {
  1027  			diff: computed.Diff{
  1028  				Renderer: List([]computed.Diff{}),
  1029  				Action:   plans.Create,
  1030  			},
  1031  			expected: "[]",
  1032  		},
  1033  		"list_create": {
  1034  			diff: computed.Diff{
  1035  				Renderer: List([]computed.Diff{
  1036  					{
  1037  						Renderer: Primitive(nil, json.Number("1"), cty.Number),
  1038  						Action:   plans.Create,
  1039  					},
  1040  				}),
  1041  				Action: plans.Create,
  1042  			},
  1043  			expected: `
  1044  [
  1045        + 1,
  1046      ]
  1047  `,
  1048  		},
  1049  		"list_delete_empty": {
  1050  			diff: computed.Diff{
  1051  				Renderer: List([]computed.Diff{}),
  1052  				Action:   plans.Delete,
  1053  			},
  1054  			expected: "[] -> null",
  1055  		},
  1056  		"list_delete": {
  1057  			diff: computed.Diff{
  1058  				Renderer: List([]computed.Diff{
  1059  					{
  1060  						Renderer: Primitive(json.Number("1"), nil, cty.Number),
  1061  						Action:   plans.Delete,
  1062  					},
  1063  				}),
  1064  				Action: plans.Delete,
  1065  			},
  1066  			expected: `
  1067  [
  1068        - 1,
  1069      ] -> null
  1070  `,
  1071  		},
  1072  		"list_create_element": {
  1073  			diff: computed.Diff{
  1074  				Renderer: List([]computed.Diff{
  1075  					{
  1076  						Renderer: Primitive(nil, json.Number("1"), cty.Number),
  1077  						Action:   plans.Create,
  1078  					},
  1079  				}),
  1080  				Action: plans.Update,
  1081  			},
  1082  			expected: `
  1083  [
  1084        + 1,
  1085      ]
  1086  `,
  1087  		},
  1088  		"list_update_element": {
  1089  			diff: computed.Diff{
  1090  				Renderer: List([]computed.Diff{
  1091  					{
  1092  						Renderer: Primitive(json.Number("0"), json.Number("1"), cty.Number),
  1093  						Action:   plans.Update,
  1094  					},
  1095  				}),
  1096  				Action: plans.Update,
  1097  			},
  1098  			expected: `
  1099  [
  1100        ~ 0 -> 1,
  1101      ]
  1102  `,
  1103  		},
  1104  		"list_replace_element": {
  1105  			diff: computed.Diff{
  1106  				Renderer: List([]computed.Diff{
  1107  					{
  1108  						Renderer: Primitive(json.Number("0"), nil, cty.Number),
  1109  						Action:   plans.Delete,
  1110  					},
  1111  					{
  1112  						Renderer: Primitive(nil, json.Number("1"), cty.Number),
  1113  						Action:   plans.Create,
  1114  					},
  1115  				}),
  1116  				Action: plans.Update,
  1117  			},
  1118  			expected: `
  1119  [
  1120        - 0,
  1121        + 1,
  1122      ]
  1123  `,
  1124  		},
  1125  		"list_delete_element": {
  1126  			diff: computed.Diff{
  1127  				Renderer: List([]computed.Diff{
  1128  					{
  1129  						Renderer: Primitive(json.Number("0"), nil, cty.Number),
  1130  						Action:   plans.Delete,
  1131  					},
  1132  				}),
  1133  				Action: plans.Update,
  1134  			},
  1135  			expected: `
  1136  [
  1137        - 0,
  1138      ]
  1139  `,
  1140  		},
  1141  		"list_update_forces_replacement": {
  1142  			diff: computed.Diff{
  1143  				Renderer: List([]computed.Diff{
  1144  					{
  1145  						Renderer: Primitive(json.Number("0"), json.Number("1"), cty.Number),
  1146  						Action:   plans.Update,
  1147  					},
  1148  				}),
  1149  				Action:  plans.Update,
  1150  				Replace: true,
  1151  			},
  1152  			expected: `
  1153  [ # forces replacement
  1154        ~ 0 -> 1,
  1155      ]
  1156  `,
  1157  		},
  1158  		"list_update_ignores_unchanged": {
  1159  			diff: computed.Diff{
  1160  				Renderer: NestedList([]computed.Diff{
  1161  					{
  1162  						Renderer: Primitive(json.Number("0"), json.Number("0"), cty.Number),
  1163  						Action:   plans.NoOp,
  1164  					},
  1165  					{
  1166  						Renderer: Primitive(json.Number("1"), json.Number("1"), cty.Number),
  1167  						Action:   plans.NoOp,
  1168  					},
  1169  					{
  1170  						Renderer: Primitive(json.Number("2"), json.Number("5"), cty.Number),
  1171  						Action:   plans.Update,
  1172  					},
  1173  					{
  1174  						Renderer: Primitive(json.Number("3"), json.Number("3"), cty.Number),
  1175  						Action:   plans.NoOp,
  1176  					},
  1177  					{
  1178  						Renderer: Primitive(json.Number("4"), json.Number("4"), cty.Number),
  1179  						Action:   plans.NoOp,
  1180  					},
  1181  				}),
  1182  				Action: plans.Update,
  1183  			},
  1184  			expected: `
  1185  [
  1186        ~ 2 -> 5,
  1187          # (4 unchanged elements hidden)
  1188      ]
  1189  `,
  1190  		},
  1191  		"list_update_ignored_unchanged_with_context": {
  1192  			diff: computed.Diff{
  1193  				Renderer: List([]computed.Diff{
  1194  					{
  1195  						Renderer: Primitive(json.Number("0"), json.Number("0"), cty.Number),
  1196  						Action:   plans.NoOp,
  1197  					},
  1198  					{
  1199  						Renderer: Primitive(json.Number("1"), json.Number("1"), cty.Number),
  1200  						Action:   plans.NoOp,
  1201  					},
  1202  					{
  1203  						Renderer: Primitive(json.Number("2"), json.Number("5"), cty.Number),
  1204  						Action:   plans.Update,
  1205  					},
  1206  					{
  1207  						Renderer: Primitive(json.Number("3"), json.Number("3"), cty.Number),
  1208  						Action:   plans.NoOp,
  1209  					},
  1210  					{
  1211  						Renderer: Primitive(json.Number("4"), json.Number("4"), cty.Number),
  1212  						Action:   plans.NoOp,
  1213  					},
  1214  				}),
  1215  				Action: plans.Update,
  1216  			},
  1217  			expected: `
  1218  [
  1219          # (1 unchanged element hidden)
  1220          1,
  1221        ~ 2 -> 5,
  1222          3,
  1223          # (1 unchanged element hidden)
  1224      ]
  1225  `,
  1226  		},
  1227  		"list_create_sensitive_element": {
  1228  			diff: computed.Diff{
  1229  				Renderer: List([]computed.Diff{
  1230  					{
  1231  						Renderer: Sensitive(computed.Diff{
  1232  							Renderer: Primitive(nil, json.Number("1"), cty.Number),
  1233  							Action:   plans.Create,
  1234  						}, false, true),
  1235  						Action: plans.Create,
  1236  					},
  1237  				}),
  1238  				Action: plans.Update,
  1239  			},
  1240  			expected: `
  1241  [
  1242        + (sensitive value),
  1243      ]
  1244  `,
  1245  		},
  1246  		"list_delete_sensitive_element": {
  1247  			diff: computed.Diff{
  1248  				Renderer: List([]computed.Diff{
  1249  					{
  1250  						Renderer: Sensitive(computed.Diff{
  1251  							Renderer: Primitive(json.Number("1"), nil, cty.Number),
  1252  							Action:   plans.Delete,
  1253  						}, true, false),
  1254  						Action: plans.Delete,
  1255  					},
  1256  				}),
  1257  				Action: plans.Update,
  1258  			},
  1259  			expected: `
  1260  [
  1261        - (sensitive value),
  1262      ]
  1263  `,
  1264  		},
  1265  		"list_update_sensitive_element": {
  1266  			diff: computed.Diff{
  1267  				Renderer: List([]computed.Diff{
  1268  					{
  1269  						Renderer: Sensitive(computed.Diff{
  1270  							Renderer: Primitive(json.Number("0"), json.Number("1"), cty.Number),
  1271  							Action:   plans.Update,
  1272  						}, true, true),
  1273  						Action: plans.Update,
  1274  					},
  1275  				}),
  1276  				Action: plans.Update,
  1277  			},
  1278  			expected: `
  1279  [
  1280        ~ (sensitive value),
  1281      ]
  1282  `,
  1283  		},
  1284  		"list_update_sensitive_element_status": {
  1285  			diff: computed.Diff{
  1286  				Renderer: List([]computed.Diff{
  1287  					{
  1288  						Renderer: Sensitive(computed.Diff{
  1289  							Renderer: Primitive(json.Number("1"), json.Number("1"), cty.Number),
  1290  							Action:   plans.NoOp,
  1291  						}, false, true),
  1292  						Action: plans.Update,
  1293  					},
  1294  				}),
  1295  				Action: plans.Update,
  1296  			},
  1297  			expected: `
  1298  [
  1299        # Warning: this attribute value will be marked as sensitive and will not
  1300        # display in UI output after applying this change. The value is unchanged.
  1301        ~ (sensitive value),
  1302      ]
  1303  `,
  1304  		},
  1305  		"list_create_computed_element": {
  1306  			diff: computed.Diff{
  1307  				Renderer: List([]computed.Diff{
  1308  					{
  1309  						Renderer: Unknown(computed.Diff{}),
  1310  						Action:   plans.Create,
  1311  					},
  1312  				}),
  1313  				Action: plans.Update,
  1314  			},
  1315  			expected: `
  1316  [
  1317        + (known after apply),
  1318      ]
  1319  `,
  1320  		},
  1321  		"list_update_computed_element": {
  1322  			diff: computed.Diff{
  1323  				Renderer: List([]computed.Diff{
  1324  					{
  1325  						Renderer: Unknown(computed.Diff{
  1326  							Renderer: Primitive(json.Number("0"), nil, cty.Number),
  1327  							Action:   plans.Delete,
  1328  						}),
  1329  						Action: plans.Update,
  1330  					},
  1331  				}),
  1332  				Action: plans.Update,
  1333  			},
  1334  			expected: `
  1335  [
  1336        ~ 0 -> (known after apply),
  1337      ]
  1338  `,
  1339  		},
  1340  		"set_create_empty": {
  1341  			diff: computed.Diff{
  1342  				Renderer: Set([]computed.Diff{}),
  1343  				Action:   plans.Create,
  1344  			},
  1345  			expected: "[]",
  1346  		},
  1347  		"set_create": {
  1348  			diff: computed.Diff{
  1349  				Renderer: Set([]computed.Diff{
  1350  					{
  1351  						Renderer: Primitive(nil, json.Number("1"), cty.Number),
  1352  						Action:   plans.Create,
  1353  					},
  1354  				}),
  1355  				Action: plans.Create,
  1356  			},
  1357  			expected: `
  1358  [
  1359        + 1,
  1360      ]
  1361  `,
  1362  		},
  1363  		"set_delete_empty": {
  1364  			diff: computed.Diff{
  1365  				Renderer: Set([]computed.Diff{}),
  1366  				Action:   plans.Delete,
  1367  			},
  1368  			expected: "[] -> null",
  1369  		},
  1370  		"set_delete": {
  1371  			diff: computed.Diff{
  1372  				Renderer: Set([]computed.Diff{
  1373  					{
  1374  						Renderer: Primitive(json.Number("1"), nil, cty.Number),
  1375  						Action:   plans.Delete,
  1376  					},
  1377  				}),
  1378  				Action: plans.Delete,
  1379  			},
  1380  			expected: `
  1381  [
  1382        - 1,
  1383      ] -> null
  1384  `,
  1385  		},
  1386  		"set_create_element": {
  1387  			diff: computed.Diff{
  1388  				Renderer: Set([]computed.Diff{
  1389  					{
  1390  						Renderer: Primitive(nil, json.Number("1"), cty.Number),
  1391  						Action:   plans.Create,
  1392  					},
  1393  				}),
  1394  				Action: plans.Update,
  1395  			},
  1396  			expected: `
  1397  [
  1398        + 1,
  1399      ]
  1400  `,
  1401  		},
  1402  		"set_update_element": {
  1403  			diff: computed.Diff{
  1404  				Renderer: Set([]computed.Diff{
  1405  					{
  1406  						Renderer: Primitive(json.Number("0"), json.Number("1"), cty.Number),
  1407  						Action:   plans.Update,
  1408  					},
  1409  				}),
  1410  				Action: plans.Update,
  1411  			},
  1412  			expected: `
  1413  [
  1414        ~ 0 -> 1,
  1415      ]
  1416  `,
  1417  		},
  1418  		"set_replace_element": {
  1419  			diff: computed.Diff{
  1420  				Renderer: Set([]computed.Diff{
  1421  					{
  1422  						Renderer: Primitive(json.Number("0"), nil, cty.Number),
  1423  						Action:   plans.Delete,
  1424  					},
  1425  					{
  1426  						Renderer: Primitive(nil, json.Number("1"), cty.Number),
  1427  						Action:   plans.Create,
  1428  					},
  1429  				}),
  1430  				Action: plans.Update,
  1431  			},
  1432  			expected: `
  1433  [
  1434        - 0,
  1435        + 1,
  1436      ]
  1437  `,
  1438  		},
  1439  		"set_delete_element": {
  1440  			diff: computed.Diff{
  1441  				Renderer: Set([]computed.Diff{
  1442  					{
  1443  						Renderer: Primitive(json.Number("0"), nil, cty.Number),
  1444  						Action:   plans.Delete,
  1445  					},
  1446  				}),
  1447  				Action: plans.Update,
  1448  			},
  1449  			expected: `
  1450  [
  1451        - 0,
  1452      ]
  1453  `,
  1454  		},
  1455  		"set_update_forces_replacement": {
  1456  			diff: computed.Diff{
  1457  				Renderer: Set([]computed.Diff{
  1458  					{
  1459  						Renderer: Primitive(json.Number("0"), json.Number("1"), cty.Number),
  1460  						Action:   plans.Update,
  1461  					},
  1462  				}),
  1463  				Action:  plans.Update,
  1464  				Replace: true,
  1465  			},
  1466  			expected: `
  1467  [ # forces replacement
  1468        ~ 0 -> 1,
  1469      ]
  1470  `,
  1471  		},
  1472  		"nested_set_update_forces_replacement": {
  1473  			diff: computed.Diff{
  1474  				Renderer: NestedSet([]computed.Diff{
  1475  					{
  1476  						Renderer: Object(map[string]computed.Diff{
  1477  							"attribute_one": {
  1478  								Renderer: Primitive(json.Number("0"), json.Number("1"), cty.Number),
  1479  								Action:   plans.Update,
  1480  							},
  1481  						}),
  1482  						Action: plans.Update,
  1483  					},
  1484  				}),
  1485  				Action:  plans.Update,
  1486  				Replace: true,
  1487  			},
  1488  			expected: `
  1489  [
  1490        ~ { # forces replacement
  1491            ~ attribute_one = 0 -> 1
  1492          },
  1493      ]
  1494  `,
  1495  		},
  1496  		"set_update_ignores_unchanged": {
  1497  			diff: computed.Diff{
  1498  				Renderer: Set([]computed.Diff{
  1499  					{
  1500  						Renderer: Primitive(json.Number("0"), json.Number("0"), cty.Number),
  1501  						Action:   plans.NoOp,
  1502  					},
  1503  					{
  1504  						Renderer: Primitive(json.Number("1"), json.Number("1"), cty.Number),
  1505  						Action:   plans.NoOp,
  1506  					},
  1507  					{
  1508  						Renderer: Primitive(json.Number("2"), json.Number("5"), cty.Number),
  1509  						Action:   plans.Update,
  1510  					},
  1511  					{
  1512  						Renderer: Primitive(json.Number("3"), json.Number("3"), cty.Number),
  1513  						Action:   plans.NoOp,
  1514  					},
  1515  					{
  1516  						Renderer: Primitive(json.Number("4"), json.Number("4"), cty.Number),
  1517  						Action:   plans.NoOp,
  1518  					},
  1519  				}),
  1520  				Action: plans.Update,
  1521  			},
  1522  			expected: `
  1523  [
  1524        ~ 2 -> 5,
  1525          # (4 unchanged elements hidden)
  1526      ]
  1527  `,
  1528  		},
  1529  		"set_create_sensitive_element": {
  1530  			diff: computed.Diff{
  1531  				Renderer: Set([]computed.Diff{
  1532  					{
  1533  						Renderer: Sensitive(computed.Diff{
  1534  							Renderer: Primitive(nil, json.Number("1"), cty.Number),
  1535  							Action:   plans.Create,
  1536  						}, false, true),
  1537  						Action: plans.Create,
  1538  					},
  1539  				}),
  1540  				Action: plans.Update,
  1541  			},
  1542  			expected: `
  1543  [
  1544        + (sensitive value),
  1545      ]
  1546  `,
  1547  		},
  1548  		"set_delete_sensitive_element": {
  1549  			diff: computed.Diff{
  1550  				Renderer: Set([]computed.Diff{
  1551  					{
  1552  						Renderer: Sensitive(computed.Diff{
  1553  							Renderer: Primitive(json.Number("1"), nil, cty.Number),
  1554  							Action:   plans.Delete,
  1555  						}, false, true),
  1556  						Action: plans.Delete,
  1557  					},
  1558  				}),
  1559  				Action: plans.Update,
  1560  			},
  1561  			expected: `
  1562  [
  1563        - (sensitive value),
  1564      ]
  1565  `,
  1566  		},
  1567  		"set_update_sensitive_element": {
  1568  			diff: computed.Diff{
  1569  				Renderer: Set([]computed.Diff{
  1570  					{
  1571  						Renderer: Sensitive(computed.Diff{
  1572  							Renderer: Primitive(json.Number("0"), json.Number("1"), cty.Number),
  1573  							Action:   plans.Update,
  1574  						}, true, true),
  1575  						Action: plans.Update,
  1576  					},
  1577  				}),
  1578  				Action: plans.Update,
  1579  			},
  1580  			expected: `
  1581  [
  1582        ~ (sensitive value),
  1583      ]
  1584  `,
  1585  		},
  1586  		"set_update_sensitive_element_status": {
  1587  			diff: computed.Diff{
  1588  				Renderer: Set([]computed.Diff{
  1589  					{
  1590  						Renderer: Sensitive(computed.Diff{
  1591  							Renderer: Primitive(json.Number("1"), json.Number("2"), cty.Number),
  1592  							Action:   plans.Update,
  1593  						}, false, true),
  1594  						Action: plans.Update,
  1595  					},
  1596  				}),
  1597  				Action: plans.Update,
  1598  			},
  1599  			expected: `
  1600  [
  1601        # Warning: this attribute value will be marked as sensitive and will not
  1602        # display in UI output after applying this change.
  1603        ~ (sensitive value),
  1604      ]
  1605  `,
  1606  		},
  1607  		"set_create_computed_element": {
  1608  			diff: computed.Diff{
  1609  				Renderer: Set([]computed.Diff{
  1610  					{
  1611  						Renderer: Unknown(computed.Diff{}),
  1612  						Action:   plans.Create,
  1613  					},
  1614  				}),
  1615  				Action: plans.Update,
  1616  			},
  1617  			expected: `
  1618  [
  1619        + (known after apply),
  1620      ]
  1621  `,
  1622  		},
  1623  		"set_update_computed_element": {
  1624  			diff: computed.Diff{
  1625  				Renderer: Set([]computed.Diff{
  1626  					{
  1627  						Renderer: Unknown(computed.Diff{
  1628  							Renderer: Primitive(json.Number("0"), nil, cty.Number),
  1629  							Action:   plans.Delete,
  1630  						}),
  1631  						Action: plans.Update,
  1632  					},
  1633  				}),
  1634  				Action: plans.Update,
  1635  			},
  1636  			expected: `
  1637  [
  1638        ~ 0 -> (known after apply),
  1639      ]
  1640  `,
  1641  		},
  1642  		"create_empty_block": {
  1643  			diff: computed.Diff{
  1644  				Renderer: Block(nil, Blocks{}),
  1645  				Action:   plans.Create,
  1646  			},
  1647  			expected: "{}",
  1648  		},
  1649  		"create_populated_block": {
  1650  			diff: computed.Diff{
  1651  				Renderer: Block(map[string]computed.Diff{
  1652  					"string": {
  1653  						Renderer: Primitive(nil, "root", cty.String),
  1654  						Action:   plans.Create,
  1655  					},
  1656  					"boolean": {
  1657  						Renderer: Primitive(nil, true, cty.Bool),
  1658  						Action:   plans.Create,
  1659  					},
  1660  				}, Blocks{
  1661  					SingleBlocks: map[string]computed.Diff{
  1662  						"nested_block": {
  1663  							Renderer: Block(map[string]computed.Diff{
  1664  								"string": {
  1665  									Renderer: Primitive(nil, "one", cty.String),
  1666  									Action:   plans.Create,
  1667  								},
  1668  							}, Blocks{}),
  1669  							Action: plans.Create,
  1670  						},
  1671  						"nested_block_two": {
  1672  							Renderer: Block(map[string]computed.Diff{
  1673  								"string": {
  1674  									Renderer: Primitive(nil, "two", cty.String),
  1675  									Action:   plans.Create,
  1676  								},
  1677  							}, Blocks{}),
  1678  							Action: plans.Create,
  1679  						},
  1680  					},
  1681  				}),
  1682  				Action: plans.Create,
  1683  			},
  1684  			expected: `
  1685  {
  1686        + boolean = true
  1687        + string  = "root"
  1688  
  1689        + nested_block {
  1690            + string = "one"
  1691          }
  1692  
  1693        + nested_block_two {
  1694            + string = "two"
  1695          }
  1696      }`,
  1697  		},
  1698  		"update_empty_block": {
  1699  			diff: computed.Diff{
  1700  				Renderer: Block(map[string]computed.Diff{
  1701  					"string": {
  1702  						Renderer: Primitive(nil, "root", cty.String),
  1703  						Action:   plans.Create,
  1704  					},
  1705  					"boolean": {
  1706  						Renderer: Primitive(nil, true, cty.Bool),
  1707  						Action:   plans.Create,
  1708  					},
  1709  				}, Blocks{
  1710  					SingleBlocks: map[string]computed.Diff{
  1711  						"nested_block": {
  1712  
  1713  							Renderer: Block(map[string]computed.Diff{
  1714  								"string": {
  1715  									Renderer: Primitive(nil, "one", cty.String),
  1716  									Action:   plans.Create,
  1717  								},
  1718  							}, Blocks{}),
  1719  							Action: plans.Create,
  1720  						},
  1721  						"nested_block_two": {
  1722  
  1723  							Renderer: Block(map[string]computed.Diff{
  1724  								"string": {
  1725  									Renderer: Primitive(nil, "two", cty.String),
  1726  									Action:   plans.Create,
  1727  								},
  1728  							}, Blocks{}),
  1729  							Action: plans.Create,
  1730  						},
  1731  					},
  1732  				}),
  1733  				Action: plans.Update,
  1734  			},
  1735  			expected: `
  1736  {
  1737        + boolean = true
  1738        + string  = "root"
  1739  
  1740        + nested_block {
  1741            + string = "one"
  1742          }
  1743  
  1744        + nested_block_two {
  1745            + string = "two"
  1746          }
  1747      }`,
  1748  		},
  1749  		"update_populated_block": {
  1750  			diff: computed.Diff{
  1751  				Renderer: Block(map[string]computed.Diff{
  1752  					"string": {
  1753  						Renderer: Primitive(nil, "root", cty.String),
  1754  						Action:   plans.Create,
  1755  					},
  1756  					"boolean": {
  1757  						Renderer: Primitive(false, true, cty.Bool),
  1758  						Action:   plans.Update,
  1759  					},
  1760  				}, Blocks{
  1761  					SingleBlocks: map[string]computed.Diff{
  1762  						"nested_block": {
  1763  							Renderer: Block(map[string]computed.Diff{
  1764  								"string": {
  1765  									Renderer: Primitive(nil, "one", cty.String),
  1766  									Action:   plans.NoOp,
  1767  								},
  1768  							}, Blocks{}),
  1769  							Action: plans.NoOp,
  1770  						},
  1771  						"nested_block_two": {
  1772  							Renderer: Block(map[string]computed.Diff{
  1773  								"string": {
  1774  									Renderer: Primitive(nil, "two", cty.String),
  1775  									Action:   plans.Create,
  1776  								},
  1777  							}, Blocks{}),
  1778  							Action: plans.Create,
  1779  						},
  1780  					},
  1781  				}),
  1782  				Action: plans.Update,
  1783  			},
  1784  			expected: `
  1785  {
  1786        ~ boolean = false -> true
  1787        + string  = "root"
  1788  
  1789        + nested_block_two {
  1790            + string = "two"
  1791          }
  1792  
  1793          # (1 unchanged block hidden)
  1794      }`,
  1795  		},
  1796  		"clear_populated_block": {
  1797  			diff: computed.Diff{
  1798  				Renderer: Block(map[string]computed.Diff{
  1799  					"string": {
  1800  						Renderer: Primitive("root", nil, cty.String),
  1801  						Action:   plans.Delete,
  1802  					},
  1803  					"boolean": {
  1804  						Renderer: Primitive(true, nil, cty.Bool),
  1805  						Action:   plans.Delete,
  1806  					},
  1807  				}, Blocks{
  1808  					SingleBlocks: map[string]computed.Diff{
  1809  						"nested_block": {
  1810  							Renderer: Block(map[string]computed.Diff{
  1811  								"string": {
  1812  									Renderer: Primitive("one", nil, cty.String),
  1813  									Action:   plans.Delete,
  1814  								},
  1815  							}, Blocks{}),
  1816  							Action: plans.Delete,
  1817  						},
  1818  						"nested_block_two": {
  1819  							Renderer: Block(map[string]computed.Diff{
  1820  								"string": {
  1821  									Renderer: Primitive("two", nil, cty.String),
  1822  									Action:   plans.Delete,
  1823  								},
  1824  							}, Blocks{}),
  1825  							Action: plans.Delete,
  1826  						},
  1827  					},
  1828  				}),
  1829  				Action: plans.Update,
  1830  			},
  1831  			expected: `
  1832  {
  1833        - boolean = true -> null
  1834        - string  = "root" -> null
  1835  
  1836        - nested_block {
  1837            - string = "one" -> null
  1838          }
  1839  
  1840        - nested_block_two {
  1841            - string = "two" -> null
  1842          }
  1843      }`,
  1844  		},
  1845  		"delete_populated_block": {
  1846  			diff: computed.Diff{
  1847  				Renderer: Block(map[string]computed.Diff{
  1848  					"string": {
  1849  						Renderer: Primitive("root", nil, cty.String),
  1850  						Action:   plans.Delete,
  1851  					},
  1852  					"boolean": {
  1853  						Renderer: Primitive(true, nil, cty.Bool),
  1854  						Action:   plans.Delete,
  1855  					},
  1856  				}, Blocks{
  1857  					SingleBlocks: map[string]computed.Diff{
  1858  						"nested_block": {
  1859  							Renderer: Block(map[string]computed.Diff{
  1860  								"string": {
  1861  									Renderer: Primitive("one", nil, cty.String),
  1862  									Action:   plans.Delete,
  1863  								},
  1864  							}, Blocks{}),
  1865  							Action: plans.Delete,
  1866  						},
  1867  						"nested_block_two": {
  1868  							Renderer: Block(map[string]computed.Diff{
  1869  								"string": {
  1870  									Renderer: Primitive("two", nil, cty.String),
  1871  									Action:   plans.Delete,
  1872  								},
  1873  							}, Blocks{}),
  1874  							Action: plans.Delete,
  1875  						},
  1876  					},
  1877  				}),
  1878  				Action: plans.Delete,
  1879  			},
  1880  			expected: `
  1881  {
  1882        - boolean = true -> null
  1883        - string  = "root" -> null
  1884  
  1885        - nested_block {
  1886            - string = "one" -> null
  1887          }
  1888  
  1889        - nested_block_two {
  1890            - string = "two" -> null
  1891          }
  1892      }`,
  1893  		},
  1894  		"list_block_update": {
  1895  			diff: computed.Diff{
  1896  				Renderer: Block(
  1897  					nil,
  1898  					Blocks{
  1899  						ListBlocks: map[string][]computed.Diff{
  1900  							"list_blocks": {
  1901  								{
  1902  									Renderer: Block(map[string]computed.Diff{
  1903  										"number": {
  1904  											Renderer: Primitive(json.Number("1"), json.Number("2"), cty.Number),
  1905  											Action:   plans.Update,
  1906  										},
  1907  										"string": {
  1908  											Renderer: Primitive(nil, "new", cty.String),
  1909  											Action:   plans.Create,
  1910  										},
  1911  									}, Blocks{}),
  1912  									Action: plans.Update,
  1913  								},
  1914  								{
  1915  									Renderer: Block(map[string]computed.Diff{
  1916  										"number": {
  1917  											Renderer: Primitive(json.Number("1"), nil, cty.Number),
  1918  											Action:   plans.Delete,
  1919  										},
  1920  										"string": {
  1921  											Renderer: Primitive("old", "new", cty.String),
  1922  											Action:   plans.Update,
  1923  										},
  1924  									}, Blocks{}),
  1925  									Action: plans.Update,
  1926  								},
  1927  							},
  1928  						},
  1929  					}),
  1930  			},
  1931  			expected: `
  1932  {
  1933        ~ list_blocks {
  1934            ~ number = 1 -> 2
  1935            + string = "new"
  1936          }
  1937        ~ list_blocks {
  1938            - number = 1 -> null
  1939            ~ string = "old" -> "new"
  1940          }
  1941      }`,
  1942  		},
  1943  		"set_block_update": {
  1944  			diff: computed.Diff{
  1945  				Renderer: Block(
  1946  					nil,
  1947  					Blocks{
  1948  						SetBlocks: map[string][]computed.Diff{
  1949  							"set_blocks": {
  1950  								{
  1951  									Renderer: Block(map[string]computed.Diff{
  1952  										"number": {
  1953  											Renderer: Primitive(json.Number("1"), json.Number("2"), cty.Number),
  1954  											Action:   plans.Update,
  1955  										},
  1956  										"string": {
  1957  											Renderer: Primitive(nil, "new", cty.String),
  1958  											Action:   plans.Create,
  1959  										},
  1960  									}, Blocks{}),
  1961  									Action: plans.Update,
  1962  								},
  1963  								{
  1964  									Renderer: Block(map[string]computed.Diff{
  1965  										"number": {
  1966  											Renderer: Primitive(json.Number("1"), nil, cty.Number),
  1967  											Action:   plans.Delete,
  1968  										},
  1969  										"string": {
  1970  											Renderer: Primitive("old", "new", cty.String),
  1971  											Action:   plans.Update,
  1972  										},
  1973  									}, Blocks{}),
  1974  									Action: plans.Update,
  1975  								},
  1976  							},
  1977  						},
  1978  					}),
  1979  			},
  1980  			expected: `
  1981  {
  1982        ~ set_blocks {
  1983            ~ number = 1 -> 2
  1984            + string = "new"
  1985          }
  1986        ~ set_blocks {
  1987            - number = 1 -> null
  1988            ~ string = "old" -> "new"
  1989          }
  1990      }`,
  1991  		},
  1992  		"map_block_update": {
  1993  			diff: computed.Diff{
  1994  				Renderer: Block(
  1995  					nil,
  1996  					Blocks{
  1997  						MapBlocks: map[string]map[string]computed.Diff{
  1998  							"list_blocks": {
  1999  								"key_one": {
  2000  									Renderer: Block(map[string]computed.Diff{
  2001  										"number": {
  2002  											Renderer: Primitive(json.Number("1"), json.Number("2"), cty.Number),
  2003  											Action:   plans.Update,
  2004  										},
  2005  										"string": {
  2006  											Renderer: Primitive(nil, "new", cty.String),
  2007  											Action:   plans.Create,
  2008  										},
  2009  									}, Blocks{}),
  2010  									Action: plans.Update,
  2011  								},
  2012  								"key:two": {
  2013  									Renderer: Block(map[string]computed.Diff{
  2014  										"number": {
  2015  											Renderer: Primitive(json.Number("1"), nil, cty.Number),
  2016  											Action:   plans.Delete,
  2017  										},
  2018  										"string": {
  2019  											Renderer: Primitive("old", "new", cty.String),
  2020  											Action:   plans.Update,
  2021  										},
  2022  									}, Blocks{}),
  2023  									Action: plans.Update,
  2024  								},
  2025  							},
  2026  						},
  2027  					}),
  2028  			},
  2029  			expected: `
  2030  {
  2031        ~ list_blocks "key:two" {
  2032            - number = 1 -> null
  2033            ~ string = "old" -> "new"
  2034          }
  2035        ~ list_blocks "key_one" {
  2036            ~ number = 1 -> 2
  2037            + string = "new"
  2038          }
  2039      }
  2040  `,
  2041  		},
  2042  		"sensitive_block": {
  2043  			diff: computed.Diff{
  2044  				Renderer: SensitiveBlock(computed.Diff{
  2045  					Renderer: Block(nil, Blocks{}),
  2046  					Action:   plans.NoOp,
  2047  				}, true, true),
  2048  				Action: plans.Update,
  2049  			},
  2050  			expected: `
  2051  {
  2052        # At least one attribute in this block is (or was) sensitive,
  2053        # so its contents will not be displayed.
  2054      }
  2055  `,
  2056  		},
  2057  		"delete_empty_block": {
  2058  			diff: computed.Diff{
  2059  				Renderer: Block(nil, Blocks{}),
  2060  				Action:   plans.Delete,
  2061  			},
  2062  			expected: "{}",
  2063  		},
  2064  		"block_escapes_keys": {
  2065  			diff: computed.Diff{
  2066  				Renderer: Block(map[string]computed.Diff{
  2067  					"attribute_one": {
  2068  						Renderer: Primitive(json.Number("1"), json.Number("2"), cty.Number),
  2069  						Action:   plans.Update,
  2070  					},
  2071  					"attribute:two": {
  2072  						Renderer: Primitive(json.Number("2"), json.Number("3"), cty.Number),
  2073  						Action:   plans.Update,
  2074  					},
  2075  					"attribute_six": {
  2076  						Renderer: Primitive(json.Number("3"), json.Number("4"), cty.Number),
  2077  						Action:   plans.Update,
  2078  					},
  2079  				}, Blocks{
  2080  					SingleBlocks: map[string]computed.Diff{
  2081  						"nested_block:one": {
  2082  							Renderer: Block(map[string]computed.Diff{
  2083  								"string": {
  2084  									Renderer: Primitive("one", "four", cty.String),
  2085  									Action:   plans.Update,
  2086  								},
  2087  							}, Blocks{}),
  2088  							Action: plans.Update,
  2089  						},
  2090  						"nested_block_two": {
  2091  							Renderer: Block(map[string]computed.Diff{
  2092  								"string": {
  2093  									Renderer: Primitive("two", "three", cty.String),
  2094  									Action:   plans.Update,
  2095  								},
  2096  							}, Blocks{}),
  2097  							Action: plans.Update,
  2098  						},
  2099  					},
  2100  				}),
  2101  				Action: plans.Update,
  2102  			},
  2103  			expected: `
  2104  {
  2105        ~ "attribute:two" = 2 -> 3
  2106        ~ attribute_one   = 1 -> 2
  2107        ~ attribute_six   = 3 -> 4
  2108  
  2109        ~ "nested_block:one" {
  2110            ~ string = "one" -> "four"
  2111          }
  2112  
  2113        ~ nested_block_two {
  2114            ~ string = "two" -> "three"
  2115          }
  2116      }`,
  2117  		},
  2118  		"block_always_includes_important_attributes": {
  2119  			diff: computed.Diff{
  2120  				Renderer: Block(map[string]computed.Diff{
  2121  					"id": {
  2122  						Renderer: Primitive("root", "root", cty.String),
  2123  						Action:   plans.NoOp,
  2124  					},
  2125  					"boolean": {
  2126  						Renderer: Primitive(false, false, cty.Bool),
  2127  						Action:   plans.NoOp,
  2128  					},
  2129  				}, Blocks{
  2130  					SingleBlocks: map[string]computed.Diff{
  2131  						"nested_block": {
  2132  							Renderer: Block(map[string]computed.Diff{
  2133  								"string": {
  2134  									Renderer: Primitive("one", "one", cty.String),
  2135  									Action:   plans.NoOp,
  2136  								},
  2137  							}, Blocks{}),
  2138  							Action: plans.NoOp,
  2139  						},
  2140  						"nested_block_two": {
  2141  							Renderer: Block(map[string]computed.Diff{
  2142  								"string": {
  2143  									Renderer: Primitive("two", "two", cty.String),
  2144  									Action:   plans.NoOp,
  2145  								},
  2146  							}, Blocks{}),
  2147  							Action: plans.NoOp,
  2148  						},
  2149  					},
  2150  				}),
  2151  				Action: plans.NoOp,
  2152  			},
  2153  			expected: `
  2154  {
  2155          id      = "root"
  2156          # (1 unchanged attribute hidden)
  2157  
  2158          # (2 unchanged blocks hidden)
  2159      }`,
  2160  		},
  2161  		"output_map_to_list": {
  2162  			diff: computed.Diff{
  2163  				Renderer: TypeChange(computed.Diff{
  2164  					Renderer: Map(map[string]computed.Diff{
  2165  						"element_one": {
  2166  							Renderer: Primitive(json.Number("0"), nil, cty.Number),
  2167  							Action:   plans.Delete,
  2168  						},
  2169  						"element_two": {
  2170  							Renderer: Primitive(json.Number("1"), nil, cty.Number),
  2171  							Action:   plans.Delete,
  2172  						},
  2173  					}),
  2174  					Action: plans.Delete,
  2175  				}, computed.Diff{
  2176  					Renderer: List([]computed.Diff{
  2177  						{
  2178  							Renderer: Primitive(nil, json.Number("0"), cty.Number),
  2179  							Action:   plans.Create,
  2180  						},
  2181  						{
  2182  							Renderer: Primitive(nil, json.Number("1"), cty.Number),
  2183  							Action:   plans.Create,
  2184  						},
  2185  					}),
  2186  					Action: plans.Create,
  2187  				}),
  2188  			},
  2189  			expected: `
  2190  {
  2191        - "element_one" = 0
  2192        - "element_two" = 1
  2193      } -> [
  2194        + 0,
  2195        + 1,
  2196      ]
  2197  `,
  2198  		},
  2199  		"json_string_no_symbols": {
  2200  			diff: computed.Diff{
  2201  				Renderer: Primitive("{\"key\":\"value\"}", "{\"key\":\"value\"}", cty.String),
  2202  				Action:   plans.NoOp,
  2203  			},
  2204  			opts: computed.RenderHumanOpts{
  2205  				HideDiffActionSymbols: true,
  2206  				ShowUnchangedChildren: true,
  2207  			},
  2208  			expected: `
  2209  jsonencode(
  2210      {
  2211          key = "value"
  2212      }
  2213  )
  2214  `,
  2215  		},
  2216  	}
  2217  	for name, tc := range tcs {
  2218  		t.Run(name, func(t *testing.T) {
  2219  
  2220  			opts := tc.opts.Clone()
  2221  			opts.Colorize = &colorize
  2222  
  2223  			expected := strings.TrimSpace(tc.expected)
  2224  			actual := tc.diff.RenderHuman(0, opts)
  2225  			if diff := cmp.Diff(expected, actual); len(diff) > 0 {
  2226  				t.Fatalf("\nexpected:\n%s\nactual:\n%s\ndiff:\n%s\n", expected, actual, diff)
  2227  			}
  2228  		})
  2229  	}
  2230  }